diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index af3ad1281..000000000 --- a/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -/.yarn/** linguist-vendored -/.yarn/releases/* binary -/.yarn/plugins/**/* binary -/.pnp.* binary linguist-generated diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89370adf4..8d16b274e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,11 +4,13 @@ on: push: branches: - main + workflow_dispatch: concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: release: + if: github.event_name == 'push' name: Release runs-on: ubuntu-latest permissions: @@ -83,3 +85,117 @@ jobs: pnpm install --frozen-lockfile pnpm exec turbo --filter=@stakekit/widget build npm publish "./$PACKAGE_DIR" + + canary: + if: github.event_name == 'workflow_dispatch' + name: Publish canary + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + id-token: write + concurrency: + group: npm-canary-publish + cancel-in-progress: false + env: + PACKAGE_DIR: packages/widget + + steps: + # actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.sha }} + fetch-depth: 0 + + - name: Install mise + run: | + curl https://mise.run | MISE_VERSION=v2026.5.6 sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH" + mise install + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Read npm release state + id: registry + shell: bash + run: | + package_name=$(node -p "require('./${PACKAGE_DIR}/package.json').name") + latest_version=$(npm view "${package_name}@latest" version) + + { + echo "package_name=${package_name}" + echo "latest_version=${latest_version}" + } >> "$GITHUB_OUTPUT" + + - name: Prepare canary version + id: canary_version + env: + LATEST_VERSION: ${{ steps.registry.outputs.latest_version }} + run: pnpm exec tsx packages/widget/scripts/prepare-canary-release.ts + + - name: Build widget + run: pnpm exec turbo --filter=@stakekit/widget build + + - name: Test widget + run: | + pnpm --filter @stakekit/widget test:unit + pnpm --filter @stakekit/widget test:dom + + - name: Check canary publish status + id: publish + shell: bash + env: + PACKAGE_NAME: ${{ steps.registry.outputs.package_name }} + VERSION: ${{ steps.canary_version.outputs.version }} + run: | + if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then + echo "${PACKAGE_NAME}@${VERSION} is already published; skipping." + echo "should_publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "should_publish=true" >> "$GITHUB_OUTPUT" + + - name: Inspect package contents + if: steps.publish.outputs.should_publish == 'true' + run: npm pack "./$PACKAGE_DIR" --dry-run + + - name: Publish canary to npm + if: steps.publish.outputs.should_publish == 'true' + run: npm publish "./$PACKAGE_DIR" --access public --tag canary + + - name: Write release summary + env: + PACKAGE_NAME: ${{ steps.registry.outputs.package_name }} + VERSION: ${{ steps.canary_version.outputs.version }} + SHOULD_PUBLISH: ${{ steps.publish.outputs.should_publish }} + run: | + if [[ "$SHOULD_PUBLISH" == "true" ]]; then + release_status="Published" + else + release_status="Already published" + fi + + { + echo "## Widget canary release" + echo + echo "| Field | Value |" + echo "| --- | --- |" + echo "| Status | ${release_status} |" + echo "| Package | ${PACKAGE_NAME} |" + echo "| Version | ${VERSION} |" + echo "| npm tag | canary |" + echo "| Branch | ${{ github.ref_name }} |" + echo "| Commit | ${{ github.sha }} |" + echo + echo "Install the moving canary channel:" + echo + echo " pnpm add ${PACKAGE_NAME}@canary" + echo + echo "Install this exact build:" + echo + echo " pnpm add ${PACKAGE_NAME}@${VERSION}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index 7eec523be..462414e08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,268 +1,50 @@ -# production -/build - -# misc -.DS_Store -*.pem -.idea - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -.env*.local +# Dependencies and package-manager state +node_modules/ +.pnpm-store/ +*.tgz -# typescript +# Build output and caches +build/ +dist/ +.next/ +out/ +.turbo/ +.cache/ +coverage/ +.nyc_output/ +playwright-report/ +test-results/ +*.lcov *.tsbuildinfo next-env.d.ts +# Environment and local configuration +.env +.env.* +!.env.example +*.local -# compiled output -lib -**/lib - -# OS -.DS_Store - -# Tests -coverage -**/coverage -.nyc_output - -.data -ormconfig.json - -## Terraform -**/.terraform/**/ - -.terraform.lock.hcl -.terraform -# .tfstate files -*.tfstate -*.tfstate.* -*.tfvars - -# Logs -logs +# Logs and diagnostics +logs/ *.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) +*-debug.log* report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -*.lcov - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Swap the comments on the following lines if you don't wish to use zero-installs -# Documentation here: https://yarnpkg.com/features/zero-installs -!.yarn/cache -#.pnp.* - -# Yarn Not-Zero-Installs -# https://yarnpkg.com/getting-started/qa/\#which-files-should-be-gitignored -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions -.turbo +# Local vendored reference repositories +@repos/ +# Editors and operating systems .DS_Store - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files +.idea/ .vscode/* !.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln +!.vscode/settings.json *.sw? - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - +# Credentials and certificates *.key *.pem *.p12 *.pfx *.crt -*.cer - -.pnpm-store \ No newline at end of file +*.cer \ No newline at end of file diff --git a/.rev-dep.config.jsonc b/.rev-dep.config.jsonc index 5691d0bec..c613d4dc7 100644 --- a/.rev-dep.config.jsonc +++ b/.rev-dep.config.jsonc @@ -1,15 +1,16 @@ { - "$schema": "https://github.com/jayu/rev-dep/blob/master/config-schema/1.7.schema.json?raw=true", - "configVersion": "1.7", + "configVersion": "1.11", + "$schema": "https://raw.githubusercontent.com/jayu/rev-dep/master/config-schema/1.11.schema.json", "rules": [ { "path": "packages/widget", "prodEntryPoints": [ "src/index.package.ts", "src/index.bundle.ts", + "src/public-api/index.package.ts", + "src/public-api/index.bundle.ts", "src/main.tsx", "src/translation/i18next.d.ts", - "src/types/purify-extend.d.ts", "src/types/window.d.ts", "src/vite-env.d.ts", "postcss.config.js", @@ -18,16 +19,213 @@ ], "devEntryPoints": [ "scripts/generate-effect-openapi.ts", - "tests/utils/setup.ts", + "scripts/prepare-canary-release.ts", + "scripts/*.test.ts", + "tests/utils/setup.browser.ts", + "tests/utils/setup.dom.ts", "tests/**/*.test.ts", "tests/**/*.test.tsx" ], - "orphanFilesDetection": { - "enabled": true - }, + "moduleBoundaries": [ + { + "name": "app", + "pattern": "src/app/**", + "allow": [ + "src/app/**", + "src/features/**", + "src/resources/**", + "src/services/**", + "src/domain/**", + "src/shared/**", + "src/public-api/**", + "src/translation/**" + ] + }, + { + "name": "features", + "pattern": "src/features/**", + "allow": [ + "src/features/**", + "src/app/config/**", + "src/app/runtime/**", + "src/resources/**", + "src/services/**", + "src/domain/**", + "src/shared/**", + "src/public-api/**" + ], + "deny": [ + "src/generated/**", + "src/services/api/*-resource-source.ts", + "src/services/api/transport.ts" + ] + }, + { + "name": "resources", + "pattern": "src/resources/**", + "allow": [ + "src/resources/**", + "src/app/runtime/**", + "src/services/**", + "src/domain/**", + "src/shared/**", + "src/public-api/**" + ], + "deny": [ + "src/features/**", + "src/generated/**", + "src/services/api/*-operations.ts", + "src/services/api/transport.ts" + ] + }, + { + "name": "services", + "pattern": "src/services/**", + "allow": [ + "src/services/**", + "src/domain/**", + "src/shared/**", + "src/generated/**", + "src/public-api/**" + ] + }, + { + "name": "domain", + "pattern": "src/domain/**", + "allow": [ + "src/domain/**", + "src/generated/**", + "src/public-api/**", + "src/shared/**" + ] + }, + { + "name": "shared", + "pattern": "src/shared/**", + "allow": ["src/shared/**", "src/domain/**"] + }, + { + "name": "generated", + "pattern": "src/generated/**", + "allow": ["src/generated/**"] + }, + { + "name": "translation", + "pattern": "src/translation/**", + "allow": ["src/translation/**", "src/shared/**"] + }, + { + "name": "public-api", + "pattern": "src/public-api/**", + "allow": ["src/public-api/**"] + } + ], + "restrictedDirectImportersDetection": [ + { + "enabled": true, + "files": ["src/generated/api/**"], + "allowImporters": [ + "src/domain/borrow/**", + "src/domain/schema/**", + "src/services/api/**", + "scripts/**", + "tests/generated/**" + ] + }, + { + "enabled": true, + "files": ["src/services/api/yield-resource-source.ts"], + "allowImporters": [ + "src/app/runtime/**", + "src/resources/**", + "src/services/wallet/bootstrap.ts", + "tests/**" + ] + }, + { + "enabled": true, + "files": ["src/services/api/legacy-resource-source.ts"], + "allowImporters": [ + "src/app/runtime/**", + "src/resources/**", + "src/services/wallet/bootstrap.ts", + "tests/**" + ] + }, + { + "enabled": true, + "files": ["src/services/api/borrow-resource-source.ts"], + "allowImporters": [ + "src/app/runtime/**", + "src/resources/**", + "tests/**" + ] + }, + { + "enabled": true, + "files": ["src/services/api/yield-operations.ts"], + "allowImporters": [ + "src/app/runtime/**", + "src/features/classic-transaction-flow/state/classic-flow-session-facade.ts", + "src/services/workflow/transaction-workflow-operations-service.ts", + "tests/**" + ] + }, + { + "enabled": true, + "files": ["src/services/api/borrow-operations.ts"], + "allowImporters": [ + "src/app/runtime/**", + "src/features/borrow-transaction-flow/state/borrow-flow-session-facade.ts", + "src/services/workflow/transaction-workflow-operations-service.ts", + "tests/**" + ] + }, + { + "enabled": true, + "files": ["src/services/api/transport.ts"], + "allowImporters": [ + "src/app/runtime/app-runtime.ts", + "src/services/api/*-operations.ts", + "src/services/api/*-resource-source.ts", + "tests/**" + ] + } + ], + "orphanFilesDetection": [ + { + "enabled": true + }, + { + // check for runtime files that are only reachable from tests + "enabled": true, + "validEntryPoints": [ + "src/index.package.ts", + "src/index.bundle.ts", + "src/public-api/index.package.ts", + "src/public-api/index.bundle.ts", + "src/main.tsx", + "src/translation/i18next.d.ts", + "src/types/window.d.ts", + "src/vite-env.d.ts", + "postcss.config.js", + "public/mockServiceWorker.js", + "vite/*.ts", + "scripts/generate-effect-openapi.ts", + "scripts/prepare-canary-release.ts", + "scripts/*.test.ts", + "tests/**/*.ts", + "tests/**/*.tsx" + ], + "graphExclude": [ + "scripts/*.test.ts", + "tests/**/*.ts", + "tests/**/*.tsx" + ] + } + ], "unusedExportsDetection": { - "enabled": true, - "ignoreFiles": ["src/types/yield-api-schema.d.ts"] + "enabled": true }, "unusedNodeModulesDetection": { "enabled": true, @@ -35,10 +233,12 @@ "@effect/openapi-generator", "@effect/platform-node", "babel-plugin-react-compiler", + "jsdom", "playwright", "postcss", "swagger2openapi", "tsx", + "typescript", "yaml" ], "pkgJsonFieldsWithBinaries": ["scripts"] diff --git a/.review/core-app/FIX_PLAN.md b/.review/core-app/FIX_PLAN.md new file mode 100644 index 000000000..2c6c7a73c --- /dev/null +++ b/.review/core-app/FIX_PLAN.md @@ -0,0 +1,43 @@ +# Core application high-risk fix plan + +## Review contract + +- Treat the current application and verified user flows as the behavioral source + of truth. OpenSpec artifacts and existing tests are not specifications. +- Use `main` only as a behavioral comparison for regressions. +- Prove each fix at a user-observable seam before accepting it. +- Keep fix verification and the subsequent discovery review independent. + +## Agreed test seams + +| Finding | Observable seam | Acceptance criterion | Status | +| --- | --- | --- | --- | +| H1 | Host configuration rerender during transaction execution | Replacing only live callback identities preserves the registry, runtime, workflow identity, and exactly-once signing/submission behavior. | Fixed and verified | +| H2 | Leaving or cancelling the classic steps route | Unmounting the workflow route interrupts deferred signing/confirmation and cannot restart it through history navigation. | Fixed and verified | +| H3 | Wallet provider after best-effort initialization failure | Reconnect, mobile fallback, or initial-chain-switch failure preserves configured connectors and permits manual recovery. | Not fixed: atom seams pass; automatic reconnect browser integration fails | +| H4 | Position details across wallet changes | Data and staged actions captured for wallet A are unavailable immediately after switching to wallet B. | Fixed and verified | +| H5 | Resolved force-max Earn form | A force-max yield resolves to the available balance, never the `-1` sentinel, and stays disabled while that balance is unknown. | Fixed and verified | +| H6 | Screens after successful transaction completion | Earn, Activity, and Borrow invalidate and refetch the exact wallet-scoped resources consumed by their visible screens. | Fixed and verified | + +## Execution waves + +1. Independently verify the staged candidate across runtime/wallet, + Earn/Activity, and position/Borrow seams. +2. Patch confirmed gaps one vertical red-green slice at a time. +3. Run focused unit, DOM, and browser tests plus package lint/type checks. +4. Assign fresh agents to complete flow-based review lanes, explicitly searching + for regressions and previously unknown lifecycle/state bugs. + +## Evidence log + +- Lint, Biome, and TypeScript passed. +- Unit: 86 files / 323 tests passed. +- DOM: 20 files / 48 tests passed. +- Focused Chromium: classic workflow 8/8, Borrow execution 6/6, Borrow position + flow 2/2, and dashboard rendering 8/8 passed. +- Full Chromium: 12 files / 55 tests passed; 9 tests failed across the known + Wagmi reconnect regression and full-suite gas, staking, deep-link, and + dashboard timeouts. Dashboard rendering passed in isolation. +- Hygiene checks passed. +- Detailed verdicts, additional findings, and report-only medium issues are in + `REPORT.md`. diff --git a/.review/core-app/REPORT.md b/.review/core-app/REPORT.md new file mode 100644 index 000000000..53375bb93 --- /dev/null +++ b/.review/core-app/REPORT.md @@ -0,0 +1,465 @@ +# Core application logic review report + +## Scope and method + +The current branch was reviewed as a complete application, with equivalent +flows on `main` used as the behavioral regression baseline. Diffs were used only +to locate moved or replaced code. OpenSpec artifacts, commit history, and +existing test assertions were not treated as specifications. + +The review covered application/runtime lifecycle, wallet initialization, +classic and dashboard routing, Earn and stake, portfolio and position details, +activity retry, Borrow, shared transaction execution, atom identity, and +resource invalidation. + +## Executive summary + +The initial review found six high-risk correctness issues and eight medium-risk +flow issues. The dominant failure patterns were: + +1. unrelated configuration identity owns lifecycle-sensitive runtime state; +2. route unmount no longer owns the lifetime of side-effecting workflows; +3. long-lived atoms and refs are not consistently scoped to wallet, route, or + position identity; +4. mutation completion refreshes parallel resource graphs instead of every + resource actually used by the affected screens. + +The high-risk findings were subsequently repaired and independently reviewed. +The round-two results below supersede the original implementation status while +preserving the initial findings as the audit trail. + +## Round-two verification (2026-07-17) + +### Outcome + +Five of the six original high-risk findings are fixed for their reported +failure modes. H3's failure-isolation behavior works at the atom seam, but H3 +is **not fixed** because automatic reconnect no longer completes after the +controller becomes ready. The second flow-based +review found four additional high-risk lifecycle or ownership defects; those +were fixed with regression tests before the scope was closed. No medium-risk +finding was fixed in this review round. + +| Finding | Verdict | Verification evidence | +| --- | --- | --- | +| H1 runtime rebuild on callback rerender | Fixed | Callback-only host rerenders preserve runtime generation and active workflow identity; signing and submission remain exactly once. | +| H2 classic workflow survives route exit | Fixed | The route guard explicitly mounts the TTL-zero workflow atom. Unmount closes the atom scope and eventually interrupts the processor; no separate route-active flag or per-operation ownership checks remain. | +| H3 wallet initialization loses topology | Not fixed | The built Wagmi configuration is exposed and transient enabled-network failures recover, but the isolated Wagmi provider browser test consistently reaches `ready: true` while remaining `connected: false`. Automatic reconnect no longer completes within 10 seconds. Read-only diagnosis confirmed the scoped fiber remains alive until atom disposal; the likely failure class is overlapping reconnect attempts interacting with Wagmi's global reconnect guard, not immediate fiber cancellation. | +| H4 position state crosses wallets | Fixed | Position data, staged action state, validator selection, and position action forms are cleared or remounted when the normalized network/address owner changes. Resource keys continue using the complete, stable wallet scope. | +| H5 force-max resolves to `-1` | Fixed | Force-max forms resolve to the available balance and remain unavailable while that balance is unknown. | +| H6 completion leaves visible resources stale | Fixed | Completion invalidates wallet-scoped Earn balances/positions, Activity, and Borrow resources. Submission invalidation runs immediately after successful broadcast and before confirmation begins. | + +### Architecture alignment follow-up + +- Removed the transaction workflow service's `routeActive` flag. Classic guards + mount the workflow atom directly; its TTL-zero scope owns eventual processor + interruption. +- Removed `WalletIdentityKey`. Ownership comparisons now use + `sameWalletScopeOwner` over normalized network/address, while cache and + invalidation keys retain the complete `WalletScopeKey`. +- Aligned Borrow execution with classic execution. Review stores one execution + input, a selector derives `BorrowTransactionWorkflowKey`, and a lifecycle + guard owns Steps and Complete. Executable router state, the action-form + execution variant, and history tombstones were removed. + +### Additional high-risk defects found and fixed + +1. **Case-sensitive wallet identities were compared as EVM addresses.** The + workflow guard and signing validation lowercased every network address, so + distinct Solana accounts differing by case could be treated as the same + signer. Identity comparison is now network-aware; EVM addresses remain + case-insensitive and non-EVM addresses retain their native casing. +2. **A successful broadcast could miss completion invalidation after route + exit.** Successful submission now records its semantic invalidations before + confirmation begins. Route exit is handled by scope interruption rather than + a separate route-active branch. +3. **Borrow execution could restart through browser history.** Returning Back + from active Borrow steps and then navigating Forward restored executable + history state and recreated the workflow. Execution is now resolved only + from the guarded workflow input/key. Leaving the guard clears that input, so + Forward redirects instead of reconstructing execution. +4. **Borrow position refresh could unmount an active nested execution route.** + Semantic invalidation temporarily made the routed parent render only its + loading/empty pane, removing Steps or Complete. Nested route ownership is + now independent of position loading and empty states, while the position + pane refreshes separately. + +### Remaining findings (report only) + +Per review scope, the following medium-risk findings remain unfixed: + +| ID | Finding | Current round-two status | +| --- | --- | --- | +| M1 | Earn intent survives implicit wallet/token/yield fallback | Still present. | +| M2 | Borrow review state is not scoped to wallet and route market | Confirmed reachable; backend action creation can occur before signing rejects the stale wallet. | +| M3 | Dashboard Activity details retain the previous wallet selection | UI can remain stale; the wallet-scoped execution guard prevents cross-wallet signing. | +| M4 | Position workflow inputs survive route/position changes | Still present. | +| M5 | Successful preview retry requires a second Confirm | Still present. | +| M6 | Dashboard Activity completion route is unregistered | Still present. | +| M7 | Dashboard validator-selection route mounts the wrong owner | Confirmed reachable. | +| M8 | Classic-to-dashboard variant switch can leave an unmatched route | Still present. | +| M9 | Duplicate queued Retry commands can repeat a wallet prompt | Confirmed. Two retries from one failure generation can make the second queued command valid again after the first retry returns to the same phase. Submission and advance commands have the analogous risk. | +| M10 | Permanent enabled-network errors retry every five seconds | The retry is scoped and stops on widget disposal, but errors such as invalid credentials are not classified as terminal and have no jitter. | +| M11 | Withdraw token selection can become stale after same-wallet position refresh | A removed token or reduced balance can remain captured by the local form and be submitted with old arguments. | +| M12 | Borrow form fallback can retarget preserved amounts | If selected market/collateral IDs disappear, fallback selection can change while the entered amount survives. | +| M13 | Dashboard stake-position completion outlet can disappear after an empty refresh | If successful completion removes the final position and the integration is not enterable, the parent can remove the active nested Complete outlet. | + +One lower-confidence race remains for future testing: because wallet +initialization is intentionally non-blocking, a delayed reconnect or initial +switch may overlap a manual connection initiated immediately after the +controller becomes available. No failure was reproduced in this review. + +### Open high-risk regression + +**H3-R1. Automatic wallet reconnect no longer completes after controller +readiness.** The browser contract reaches the authoritative built Wagmi config, +but `useAccount()` remains disconnected. This reproduced both in the complete +Chromium run and in an isolated two-file rerun. A mounted-atom probe confirmed +the `forkScoped` child starts, remains live after the parent publishes Success, +and stops only on registry disposal. The likely failure class is overlapping +background reconnects: Wagmi can return an empty result while a module-global +reconnect guard is active, and this initialization path does not retry that +result. The exact browser ordering still needs instrumentation. Per the +instruction to stop fixing, no production change was made after this regression +was confirmed. + +### Validation + +- Widget lint, Biome, and TypeScript: passed. +- Unit: 86 files, 323 tests passed. +- DOM: 20 files, 48 tests passed. +- Hygiene: dependency, cycle, orphan, boundary, unresolved-import, unused + export, and test-only export checks passed. +- Focused wallet-bootstrap atom verification: passed, including injected scope + cleanup and rejection behavior. Browser integration does not pass automatic + reconnect and therefore overrides the atom-only H3 acceptance verdict. +- Focused classic workflow, semantic invalidation, wallet ownership, Borrow + execution history, and Borrow routed-parent regressions: passed. +- Focused Chromium after the architecture alignment: classic workflow 8/8, + Borrow execution 6/6, Borrow position flow 2/2, and dashboard rendering 8/8 + passed. +- Full Chromium run: 12 files / 55 tests passed; 5 files / 9 tests failed. The + known Wagmi reconnect regression reproduced. Dashboard rendering passed when + isolated; the remaining failures were full-suite gas, staking, and deep-link + timeouts outside the changed seams. + +## High-risk findings + +### H1. Callback-only host rerenders rebuild the runtime and restart active workflows + +**Confidence:** High. **Baseline:** Branch regression. + +A normal host rerender with a newly created `tracking.trackEvent` or +`tracking.trackPageView` function changes `widgetBootstrapConfigAtom`. The +entire `Layer.fresh` application runtime is built from that value, so every +mounted `appRuntime.atom` is restarted, including active transaction machines. +The old machine loses its accumulated submissions and the replacement starts +from the original workflow key in `Signing` or `Confirming`. This can request a +second signature or rebroadcast a transaction already sent by the disposed +machine. + +References: + +- `packages/widget/src/app/config/widget-config.ts:19-22,41-48` +- `packages/widget/src/app/runtime/app-runtime.ts:21-58` +- `packages/widget/src/features/transaction-flow/state/transaction-workflow-atoms.ts:32-62` +- `packages/widget/src/services/workflow/transaction-workflow-service.ts:37-67` +- `packages/widget/src/services/workflow/transaction-workflow-model.ts:332-355` + +Regression test: start a deferred workflow, update only the tracking callback +identity through `widgetConfigAtom`, and assert machine identity, state, and +sign/submit call counts remain unchanged. + +### H2. Leaving the classic steps route does not stop transaction execution + +**Confidence:** High. **Baseline:** Branch regression. + +Cancel only navigates away. The workflow state, completion listener, and machine +atoms retain a five-minute idle TTL, so Effect AtomRegistry delays finalization +and the `forkScoped` processor remains active. A wallet request resolved after +the route is gone can still be submitted, confirmed, and followed by later +transactions and wallet prompts. `main` used a component-owned XState actor; +unmount stopped the actor and explicitly cleared confirmation timeouts. + +References: + +- `packages/widget/src/features/transaction-flow/ui/steps/hooks/use-steps.hook.ts:53-58` +- `packages/widget/src/features/transaction-flow/state/transaction-workflow-atoms.ts:32-105` +- `packages/widget/src/shared/config/widget-defaults.ts:8-10` +- `packages/widget/src/services/workflow/transaction-workflow-service.ts:37-67` +- `packages/widget/src/services/workflow/transaction-workflow-runtime/processor.ts:100-170` + +Regression test: defer signing or confirmation, unmount the steps route, and +assert that the TTL-zero workflow scope is eventually interrupted and cannot be +restarted through Forward navigation. + +### H3. Recoverable wallet initialization failures discard the usable wallet topology + +**Confidence:** High. **Baseline:** Branch regression. + +The branch constructs the Wagmi configuration and then runs reconnect, mobile +fallback connect, and initial chain switching as part of the same failing atom. +If any best-effort operation rejects, `walletControllerAtom` fails and +`WagmiConfigProvider` replaces the already-built configuration with the empty +fallback configuration. The widget then has no usable connectors or recovery +path until remount or a topology-changing config update. `main` retained the +configuration when these initialization attempts failed. + +References: + +- `packages/widget/src/features/wallet/wagmi/initialization.ts:123-171` +- `packages/widget/src/features/wallet/wagmi/controller.ts:42-76` +- `packages/widget/src/features/wallet/runtime/root-atom.ts:117-131` +- `packages/widget/src/features/wallet/react/provider.tsx:6-14` +- `packages/widget/src/services/wallet/default-wagmi-config.ts:17-25` + +Regression test: reject `switchChain` after configuration construction and +assert the configured connector list is still exposed and manual connect works. + +### H4. Dashboard position state can cross wallet identities + +**Confidence:** High. **Baseline:** Invariant defect also present on `main`. + +Dashboard position details retain the last non-null position balances in refs. +When wallet A changes to wallet B and B has no matching position, the new null +result is ignored and A's position remains rendered indefinitely. Exit and +pending-action builders then combine A's retained balances and pending actions +with B's current address. The request can pass signing validation because both +the newly created action and current wallet use B, even though its values came +from A. + +References: + +- `packages/widget/src/features/position-details/ui/classic/state/index.tsx:59-120` +- `packages/widget/src/features/position-details/ui/classic/hooks/use-stake-exit-request-dto.ts:20-102` +- `packages/widget/src/features/position-details/ui/classic/hooks/use-pending-actions.ts:146-185` +- `packages/widget/src/app/routes/dashboard-routes.tsx:125-183` + +Regression test: render A's position, switch to B with an empty positions +response, and require the position/action panel to clear before any request can +be created. + +### H5. Force-max Earn integrations initialize the amount to `-1` + +**Confidence:** High. **Baseline:** Branch regression. + +The force-max contract is represented by `minimum === -1 && maximum === -1`. +The current form resolver uses the raw minimum as its default amount, producing +`-1`, while validation independently maps the minimum and maximum to the wallet +balance. The flow is invalid until the user explicitly presses Max, and can be +blocked where that action is unavailable. `main` initialized force-max forms to +the available balance. + +References: + +- `packages/widget/src/domain/types/stake.ts:69-105` +- `packages/widget/src/features/earn/react/use-max-min-yield-amount.ts:28-64` +- `packages/widget/src/features/earn/state/atoms-state/resolver/form.ts:46-60` +- `packages/widget/src/features/earn/ui/classic/earn-page/state/earn-page-model.tsx:534-570` + +Regression test: resolve a force-max yield with available balance `10` and +assert `view.form.stakeAmount === "10"`, not `"-1"`. + +### H6. Successful mutations leave the resources used by affected screens stale + +**Confidence:** High. **Baseline:** Mixed branch regressions and new invariant +defects. + +The completion handlers refresh parallel or list resources, but omit several +feature-owned resources used by the visible screens: + +- Earn completion refreshes portfolio balance scans, but Earn derives its form + from separate private token-balance and catalog-position atoms with five-minute + SWR. A quick return can show pre-stake balance and minimum values. +- Activity pages and filter counts are never refreshed after completion. + Remounting their `Atom.pull` within the five-minute idle TTL reuses the old + accumulated pages without another API call. +- Borrow completion refreshes `borrowPositionsAtom`, but the routed position + page reads the distinct `borrowPositionAtom`. The parent details route remains + mounted through review, steps, and completion, so returning from a successful + repay/withdraw/toggle continues to show the old position and pending actions. + +References: + +- `packages/widget/src/features/transaction-flow/state/transaction-workflow-atoms.ts:15-30,80-96` +- `packages/widget/src/features/earn/state/atoms-state/catalog/atoms.ts:55-59,172-190,367-386,520-534` +- `packages/widget/src/features/activity/react/use-activity-actions.ts:45-107,282-300` +- `packages/widget/src/features/borrow/atoms/refresh.ts:49-83` +- `packages/widget/src/features/borrow/atoms/resources.ts:126-168` +- `packages/widget/src/features/borrow/ui/use-borrow-positions.ts:38-54` +- `packages/widget/src/features/borrow/ui/position-details.tsx:1178-1299` + +Regression tests should drive each mutation to completion and require the exact +resources consumed by Earn, Activity, and Borrow position details to issue a +new request and display the new state. + +## Medium-risk findings + +### M1. Earn form intent survives implicit wallet/token/yield scope changes + +**Confidence:** High. **Baseline:** Branch regression. + +Network change or disconnect/reconnect can make token and yield resolution fall +back to a new scope while retaining the previous amount, provider ID, Tron +resource, and max-amount flag. Explicit token/yield actions reset these fields, +but resolver-driven selection changes do not. A request for the new yield can +therefore include a provider belonging to the previous yield. + +References: + +- `packages/widget/src/features/earn/state/atoms-state/machine/atoms.ts:16-60` +- `packages/widget/src/features/earn/state/atoms-state/machine/reducer.ts:6-49` +- `packages/widget/src/features/earn/state/atoms-state/resolver/token.ts:16-60` +- `packages/widget/src/features/earn/state/atoms-state/resolver/yield.ts:53-105` +- `packages/widget/src/features/earn/state/atoms-state/resolver/form.ts:18-27,55-59` +- `packages/widget/src/features/earn/ui/classic/earn-page/state/use-stake-enter-request-dto.ts:96-110` + +### M2. Borrow review/execution state is not scoped to the current wallet or route + +**Confidence:** High. **Baseline:** New invariant defect. + +`borrowActionFormAtom` is a single registry-wide state value. The Borrow route +guard verifies only that some supported wallet is connected, and review prefers +history state or the global staged state without comparing its address/network +or market with the current wallet and route. Switching from wallet A to B on +Review lets Confirm call the Borrow API with A's staged request; wallet mismatch +is detected only later during transaction signing, after the backend action has +already been created. A stale market review can likewise be rendered under a +different position route. + +References: + +- `packages/widget/src/features/borrow/atoms/action-form.ts:107-140` +- `packages/widget/src/features/borrow/ui/connected-wallet.tsx:10-17` +- `packages/widget/src/features/borrow/ui/review.tsx:52-70,130-143` +- `packages/widget/src/services/workflow/transaction-workflow-runtime/signing.ts:28-73` + +### M3. Dashboard activity retry remains bound to the previous wallet + +**Confidence:** High. **Baseline:** Invariant defect also present on `main`. + +Selecting activity writes a keep-alive selection and unmounts the Activity page +that owns wallet-change cleanup. Account/network changes while details are open +therefore leave the old action retryable through the new `WalletService`. +Signing normally fails late on identity validation rather than clearing the +invalid flow immediately. + +References: + +- `packages/widget/src/features/activity/state/selection.ts:24-55` +- `packages/widget/src/features/activity/ui/dashboard/activity/index.tsx:20-54` +- `packages/widget/src/features/activity/ui/dashboard/activity/activity.page.tsx:93-108` + +### M4. Position workflow inputs leak across route unmounts and positions + +**Confidence:** High. **Baseline:** Branch regression. + +Unstake amount, max-amount selection, and pending-action input live in one +keep-alive `positionDetailsWorkflowAtom`. A newly mounted hook initializes its +`previousWorkflowKey` to the new current key, so its reset effect sees no +transition and preserves state inherited from the previously unmounted +position. `main` used provider-local reducer state that was discarded on +unmount. + +References: + +- `packages/widget/src/features/position-details/state/workflow.ts:35-40,75-78` +- `packages/widget/src/features/position-details/ui/classic/state/index.tsx:238-247` + +### M5. A successful stake-preview retry requires a second Confirm click + +**Confidence:** High. **Baseline:** Branch regression. + +When initial preview fails, Confirm calls `refetch()` and returns. A successful +refetch has no continuation that navigates to steps; the user must click Confirm +again. `main` awaited the retry and continued on the same click. + +References: + +- `packages/widget/src/features/transaction-flow/ui/review/hooks/use-stake-review.hook.ts:162-203` + +### M6. Dashboard activity completion navigates to an unregistered route + +**Confidence:** High. **Baseline:** Invariant defect also present on `main`. + +Successful retry steps navigate relatively to +`/activity/:pendingActionType/complete`, but dashboard routes register only the +corresponding `steps` child. The details shell remains with an empty outlet +instead of showing completion. + +References: + +- `packages/widget/src/features/transaction-flow/ui/review/hooks/use-action-review.hook.ts:90-124` +- `packages/widget/src/features/transaction-flow/ui/steps/hooks/use-steps.hook.ts:33-51` +- `packages/widget/src/app/routes/dashboard-routes.tsx:188-203` + +### M7. The dashboard validator-selection child route mounts the wrong page + +**Confidence:** High. **Baseline:** Invariant defect also present on `main`. + +The outer position route already renders `DashboardPositionDetailsPage`, but +its validator-selection child renders a second full +`DashboardPositionDetailsPage`. The component that owns and renders the +`SelectValidator` modal is absent, so the registered route cannot present the +selection UI or continue the pending action. + +References: + +- `packages/widget/src/app/routes/dashboard-routes.tsx:125-151` +- `packages/widget/src/features/position-details/ui/dashboard/index.tsx:54-104` +- `packages/widget/src/features/position-details/ui/classic/hooks/use-pending-actions.ts:120-144` +- `packages/widget/src/features/position-details/ui/dashboard/components/position-details-actions.tsx:225-260` + +### M8. Switching classic Positions to dashboard through rerender blanks the widget + +**Confidence:** High. **Baseline:** Invariant defect also present on `main`. + +The memory router survives supported prop rerenders. If classic is at +`/positions` and `dashboardVariant` becomes true, dashboard routes have neither +a `/positions` route nor a catch-all redirect, so no shell or page matches. + +References: + +- `packages/widget/src/App.tsx:25-31,41-64,68-97` +- `packages/widget/src/app/routes/dashboard-routes.tsx:95-210` + +## Hardening issue not promoted to the main list + +Unstake and pending-action review hooks dereference nullable request atoms with +non-null assertions, while their review routes are outside workflow-state +guards. A custom/direct memory-router entry can crash. The public widget creates +its own memory router at `/`, so a fresh-registry direct review entry was not +confirmed through the supported external API. The routes should still guard +ephemeral request state for consistency with steps and completion. + +References: + +- `packages/widget/src/app/routes/classic-routes.tsx:179-209` +- `packages/widget/src/app/routes/dashboard-routes.tsx:153-180` +- `packages/widget/src/features/transaction-flow/ui/review/hooks/use-unstake-review.hook.ts:33-45` +- `packages/widget/src/features/transaction-flow/ui/review/hooks/use-pending-review.hook.ts:25-48` + +## Validation performed + +- TypeScript no-emit check passed. +- Focused unit suites passed: 13 files, 48 tests covering transaction workflow + model/runtime/atoms and Borrow domain/atoms. +- Focused browser suites passed: 3 files, 8 tests covering classic transaction + execution, Borrow execution, and Borrow position details. +- The focused wallet-atom unit suite passed. + +These green suites do not exercise the state-transition sequences above; each +finding includes the missing regression scenario that should be added before or +with its fix. + +## Recommended fix order + +1. Decouple runtime identity from dynamic tracking callbacks and make workflow + ownership explicit. +2. Stop side-effecting workflow processors immediately when their route owner + exits. +3. Preserve usable wallet topology across initialization failures. +4. Introduce wallet/route/position-scoped reset rules for long-lived flow state. +5. Centralize mutation-to-resource invalidation and refresh every resource + actually consumed by affected screens. +6. Fix the force-max resolver and the isolated routing/retry defects. diff --git a/.review/core-app/REVIEW_PLAN.md b/.review/core-app/REVIEW_PLAN.md new file mode 100644 index 000000000..365be2177 --- /dev/null +++ b/.review/core-app/REVIEW_PLAN.md @@ -0,0 +1,118 @@ +# Core application logic review + +## Goal + +Review the current branch as a coherent application and identify concrete logic, +state-management, lifecycle, and flow bugs. The review is primarily behavioral: +understand how each end-to-end flow works on `main`, understand how the same flow +works on the current branch, and compare their observable behavior and required +state invariants. + +This is not a review of individual commits, migration steps, naming, formatting, +or architectural taste. Architectural concerns are findings only when they create +a concrete correctness, lifecycle, isolation, or maintainability defect with a +realistic failure sequence. + +## Sources of truth + +1. The complete implementation and observable behavior on `main` is the + regression baseline. +2. The complete implementation on the current branch is the review target. +3. Public package behavior and integration examples may clarify externally + observable contracts when the two implementations are ambiguous. + +Do not use OpenSpec artifacts or commit history to establish intended behavior. +Existing tests are incomplete and may be stale. They may be used to learn how to +exercise a flow, detect regressions, or verify a finding, but their assertions +are not authoritative specifications. + +Use `git diff` only as a navigation aid for locating moved, removed, or replaced +code. Do not review the branch commit-by-commit or treat each changed hunk as the +unit of review. + +`main` is a regression baseline, not proof that behavior is correct. Report a +current-branch logic bug even if there is no direct divergence from `main`, but +clearly distinguish an invariant-based defect from a demonstrated regression. + +## Review method + +For each owned flow: + +1. Map the entry points, routes, state owners, service boundaries, persistence, + and external inputs on `main` and on the current branch. +2. Trace the happy path end to end on both implementations. +3. Trace disruptive transitions and boundary conditions: + - wallet connection and disconnection; + - account, address, or chain changes; + - route exit, back navigation, deep links, and direct route entry; + - host configuration and initialization changes; + - overlapping requests and out-of-order async completion; + - transaction rejection, cancellation, retry, partial completion, and resume; + - component unmount and widget remount; + - resource invalidation and refresh after successful mutations; + - stale selection, workflow, or cached state crossing between identities. +4. State the behavioral invariants that should remain true and verify whether + the current implementation preserves them. +5. Follow state through UI adapters, atoms, services, workflow execution, and + resource refresh. Do not stop at a single folder boundary. +6. Reproduce credible issues with a focused test, command, or fully specified + execution sequence where practical. + +The initial review pass is read-only. Do not modify production code or tests. + +## Review lanes + +### Application lifecycle and navigation + +Runtime creation and disposal, provider composition, configuration and root +inputs, routing, route guards, initial tabs, deep links, wallet/account/chain +transitions, and widget unmount/remount behavior. + +### Earn and stake + +Earn catalog and selection resolution, token/yield/validator state, form and +amount validation, stake request creation, review, execution steps, completion, +retry/cancellation, and post-transaction refresh. + +### Portfolio, position details, and activity + +Portfolio resources and summaries, position selection/details, unstake and +pending-action creation, activity selection and reconstruction, execution, +navigation, and resource invalidation after mutations. + +### Borrow and horizontal state architecture + +Borrow form, market/position identity, wallet projection, action creation, +review/execution/completion, resource refresh, plus shared atom identity and +keys, service lifetimes, workflow isolation, cancellation, and transaction +runtime behavior used across features. + +Reviewers own their end-to-end lane but should inspect shared dependencies when +needed. Cross-lane concerns should be reported rather than silently assumed to +belong to another reviewer. + +## Finding standard + +Only report actionable correctness findings. Each finding must include: + +- severity and confidence; +- user-visible or integration impact; +- preconditions; +- the exact state/event sequence that triggers the issue; +- expected behavior, identified as either `main` behavior or an explicit + correctness invariant; +- current-branch behavior; +- precise file and line references; +- reproduction evidence or a concrete proposed regression test; +- why the behavior is not merely an intentional implementation difference. + +Keep unverified suspicions, ambiguities, and follow-up questions separate from +confirmed findings. Do not inflate the report with style feedback, broad praise, +or descriptions of code that is working correctly. + +## Coordination + +Each reviewer returns findings independently to the coordinating reviewer and +does not edit a shared findings file. The coordinator deduplicates cross-lane +findings, validates high-severity claims, and produces the final prioritized +report. No fixes are made until the review report is agreed upon. diff --git a/.scratch/atom-owned-feature-facades/issues/01-runtime-navigation-pending-actions.md b/.scratch/atom-owned-feature-facades/issues/01-runtime-navigation-pending-actions.md new file mode 100644 index 000000000..2fad64d60 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/01-runtime-navigation-pending-actions.md @@ -0,0 +1,12 @@ +# 01 — Add runtime navigation through pending-action deep links + +**What to build:** Application-owned commands can navigate through the Widget Instance's memory router without publishing a navigation outcome for React to consume. Pending-action deep links are the first complete journey to use that capability. + +**Blocked by:** None — can start immediately. + +**Status:** complete + +- [x] A runtime-scoped navigation capability supports canonical absolute push, replace, back, and scroll-reset decisions. +- [x] Pending-action deep-link commands navigate directly and no React effect coordinates their delivery. +- [x] Production and in-memory test adapters demonstrate isolation between sequential Widget Instances. +- [x] Declarative route guards and view-local navigation remain React-owned. diff --git a/.scratch/atom-owned-feature-facades/issues/02-flow-navigation.md b/.scratch/atom-owned-feature-facades/issues/02-flow-navigation.md new file mode 100644 index 000000000..990e2fa04 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/02-flow-navigation.md @@ -0,0 +1,12 @@ +# 02 — Move Classic and Borrow workflow navigation into atoms + +**What to build:** Classic and Borrow Transaction Flow commands navigate directly after their ownership and stale-result checks, so Review, Steps, and Complete transitions no longer depend on React navigation outcomes. + +**Blocked by:** 01 — Add runtime navigation through pending-action deep links. + +**Status:** complete + +- [x] Classic Transaction Flow transitions navigate through the runtime capability. +- [x] Borrow Transaction Flow transitions navigate through the runtime capability. +- [x] React navigation outcome effects and rendering adapters are removed. +- [x] Flow Session and Execution Attempt lifecycle guarantees remain covered by registry-level tests. diff --git a/.scratch/atom-owned-feature-facades/issues/03-yield-summary.md b/.scratch/atom-owned-feature-facades/issues/03-yield-summary.md new file mode 100644 index 000000000..78c64155f --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/03-yield-summary.md @@ -0,0 +1,12 @@ +# 03 — Introduce Yield Summary across read-only journeys + +**What to build:** Users see the same provider, reward-token, and semantic yield information across activity, portfolio, Review, and Complete, supplied by one shared Yield Summary capability with normalized loading and failure states. + +**Blocked by:** None — can start immediately. + +**Status:** complete + +- [x] Yield Summary exposes stable read-only view Atoms without nested Atoms, factories, or callbacks. +- [x] Read-only activity, portfolio, Review, and Complete consumers use Yield Summary. +- [x] Loading, unavailable data, and typed failures have consistent projections. +- [x] Existing visible behavior and copy remain unchanged. diff --git a/.scratch/atom-owned-feature-facades/issues/04-yield-entry-position-details.md b/.scratch/atom-owned-feature-facades/issues/04-yield-entry-position-details.md new file mode 100644 index 000000000..453ccd08c --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/04-yield-entry-position-details.md @@ -0,0 +1,13 @@ +# 04 — Introduce Yield Entry through position details + +**What to build:** A user entering or exiting a position receives shared amount constraints, validation, KYC, rewards, Action Command preparation, submission, tracking, flow start, and navigation behavior owned outside React. + +**Blocked by:** 01 — Add runtime navigation through pending-action deep links; 03 — Introduce Yield Summary across read-only journeys. + +**Status:** complete + +- [x] Position details consumes one Yield Entry facade for its complete entry journey. +- [x] React event handlers only normalize events and dispatch synchronous commands. +- [x] RainbowKit modal commands are installed through one runtime-scoped boundary adapter. +- [x] The module-global Ledger modal callback and callback-bearing Ledger command are removed. +- [x] Yield Entry behavior is covered at its public facade seam. diff --git a/.scratch/atom-owned-feature-facades/issues/05-earn-selection-facades.md b/.scratch/atom-owned-feature-facades/issues/05-earn-selection-facades.md new file mode 100644 index 000000000..69f619555 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/05-earn-selection-facades.md @@ -0,0 +1,12 @@ +# 05 — Replace the Earn browsing bridge with capability facades + +**What to build:** Users can select tokens, yields, and validators and can search, filter, paginate, and retry through stable Earn capability facades instead of an aggregate React-owned page model. + +**Blocked by:** None — can start immediately. + +**Status:** complete + +- [x] Token, yield, and validator views publish plain values through stable read-only Atoms. +- [x] Search, debounce, filtering, pagination, retry routing, and selection are stable command Atoms. +- [x] Classic and dashboard browsing surfaces consume the capability facades. +- [x] Earn Selection remains the sole source of selection, readiness, and intent decisions. diff --git a/.scratch/atom-owned-feature-facades/issues/06-earn-yield-entry.md b/.scratch/atom-owned-feature-facades/issues/06-earn-yield-entry.md new file mode 100644 index 000000000..eeca72df8 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/06-earn-yield-entry.md @@ -0,0 +1,12 @@ +# 06 — Complete Earn Yield Entry and remove the aggregate page model + +**What to build:** Both Earn variants use Yield Entry and Yield Summary for amount, quote, readiness, CTA, failures, and submission, eliminating the Atom-to-React page-model bridge. + +**Blocked by:** 04 — Introduce Yield Entry through position details; 05 — Replace the Earn browsing bridge with capability facades. + +**Status:** complete + +- [x] Classic and dashboard Earn entry surfaces consume stable feature facades. +- [x] Published view values contain no nested Atoms, Atom factories, or command callbacks. +- [x] The aggregate Earn page model, binding Atom, and bridge hook are deleted. +- [x] Earn initialization, retry, KYC, tracking, and transaction-start behavior remain unchanged. diff --git a/.scratch/atom-owned-feature-facades/issues/07-contract-and-verify.md b/.scratch/atom-owned-feature-facades/issues/07-contract-and-verify.md new file mode 100644 index 000000000..94d2f6e81 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/07-contract-and-verify.md @@ -0,0 +1,12 @@ +# 07 — Contract legacy hooks and verify the final architecture + +**What to build:** Every migrated journey uses the new facades directly, obsolete compatibility surfaces are removed, and architecture checks prove that React remains a thin view layer. + +**Blocked by:** 02 — Move Classic and Borrow workflow navigation into atoms; 06 — Complete Earn Yield Entry and remove the aggregate page model. + +**Status:** complete + +- [x] Remaining legacy consumers migrate and obsolete hooks and compatibility adapters are deleted. +- [x] Application-logic modules do not import React and touched view adapters do not synchronize domain state with effects. +- [x] Static checks reject nested public Atoms and React-owned application navigation bridges. +- [x] Focused seam tests, lint and typechecking, and the complete suite pass without public API or copy changes. diff --git a/.scratch/atom-owned-feature-facades/issues/08-application-runtime-router.md b/.scratch/atom-owned-feature-facades/issues/08-application-runtime-router.md new file mode 100644 index 000000000..9ca311975 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/issues/08-application-runtime-router.md @@ -0,0 +1,15 @@ +# 08 — Move memory-router ownership into the Application Runtime Generation + +**What to build:** A scoped synchronous Application Router runtime owns and disposes the memory router, exposes it only to the React composition boundary, and supplies its Effect Context to application navigation without a React or adapter bridge. + +**Blocked by:** 07 — Contract legacy hooks and verify the final architecture. + +**Status:** complete + +- [x] The first Application Router Atom read synchronously returns the existing memory-router type without a loading or fallback state. +- [x] `WidgetNavigation` is constructed directly from `ApplicationRouter` and preserves canonical paths, history operations, typed failures, and scroll-reset policy. +- [x] React only reads the runtime-owned router and passes it to the DOM `RouterProvider`. +- [x] Live settings changes preserve router identity while API-identity replacement and sequential remount receive fresh memory history. +- [x] Closing the Application Runtime Generation disposes the router exactly once. +- [x] The navigation adapter type, adapter Atom, provider input, React forwarding object, and adapter-specific test utilities are removed. +- [x] Classic and Dashboard route definitions remain declarative, and the router remains a memory router. diff --git a/.scratch/atom-owned-feature-facades/spec.md b/.scratch/atom-owned-feature-facades/spec.md new file mode 100644 index 000000000..4e17f25c2 --- /dev/null +++ b/.scratch/atom-owned-feature-facades/spec.md @@ -0,0 +1,247 @@ +# Atom-owned feature facades, Yield Entry, and application navigation + +Status: implemented + +## Problem Statement + +The Earn page currently concentrates a large amount of view-model and workflow logic in one React-owned model. That model subscribes to state, performs filtering, grouping, formatting, validation, request construction, pagination routing, CTA decisions, navigation, tracking, and third-party integration, then republishes the aggregate through a layout-effect-backed Atom for descendant consumers. Some values published by the underlying Earn state are themselves Atom identities or factories, so React must read one Atom to discover and subscribe to another. This Atom-to-React-to-Atom binding obscures ownership, causes broad subscriptions, and creates a second presentation-owned state machine alongside the existing Earn machine. + +The same deterministic calculations and resource projections are packaged as React hooks and imported by position details, transaction review and completion, activity, and portfolio. Those consumers therefore depend backwards on Earn's React layer, and fixes to amount constraints, provider details, reward projections, KYC state, yield type, or Action Command construction remain distributed across hook call sites. + +Application-owned navigation has a similar bridge. Classic and Borrow Transaction Flows and pending-action deep links publish Atom outcomes that React route adapters observe and convert into React Router navigation. This adds outcome-delivery state and lifecycle coordination even though the Widget Instance already owns an explicit memory router. Submission handlers in Earn and position details also combine workflow decisions, session start, analytics, and navigation inside React. + +After moving navigation decisions into `WidgetNavigation`, router construction still crosses the boundary in the opposite direction. React creates and retains the memory router, wraps it in a forwarding adapter, seeds that adapter into the Atom registry, and the application runtime reads it back to construct its navigation Layer. This models a construction-time dependency as mutable reactive input, leaves router disposal implicit, and conflicts with React Router guidance that data routers be created outside the React tree. + +The result is difficult to test at a stable interface, hard to navigate for maintainers and agents, and inconsistent with the established rule that Effect and Effect Atom own application logic. The architecture needs stable feature facades, shared Yield Entry and yield-summary capabilities, and a headless navigation seam without changing visible product behavior or public embedding interfaces. + +## Solution + +Replace callback-rich aggregate React models with capability-oriented feature facades. Each facade exposes stable read-only view Atoms and writable command Atoms while keeping mutable state, dynamic Authoritative Resource identities, pagination, retry routing, and command implementations private. React reads the smallest relevant capability and synchronously dispatches user intent. Public view values never contain nested Atoms, Atom factories, or callback functions. + +Keep the existing Earn machine as the sole source of truth for Earn Selection, readiness, failure, and user intent. Build the Earn page facade as derived capabilities for token options, yield options, validators, amount and quote, rewards and providers, submission, validation, CTA state, and page failure. Move search normalization, filtering, grouping, validator request debounce, dynamic pagination routing, and loading projections into Atom state. Leave translation and genuinely local presentation state in React. + +Introduce two cross-feature deep modules. `YieldSummary` owns read-only semantic projections such as provider details, reward-token details, yield type, and their normalized resource state. `YieldEntry` owns the pre-execution attempt to add tokens to an Earn Selection: amount constraints, validation, KYC projection, estimated rewards, Enter Action Command construction, submission eligibility, CTA decisions, command effects, analytics, transaction-session start, and navigation. Each feature supplies an input Atom and consumes the module's stable view and command interface, preserving the feature's own route or session lifetime. + +Migrate every consumer of the legacy shared React hooks. Follow any required dependency upstream until it reaches an immutable route/session input, an existing feature Atom, or an Authoritative Resource; do not publish changing hook results into writable Atoms as a replacement bridge. Retain unrelated logic in surrounding features, but delete the migrated hooks and the Earn aggregate model once no callers remain. + +Add `WidgetNavigation` to the application runtime. A separate synchronous base runtime constructs a scoped `ApplicationRouter` Layer around the existing memory router and provides its Effect Context to the application runtime, which constructs `WidgetNavigation` directly from that service. An internal Atom synchronously exposes the runtime-owned router only to the React composition boundary for `RouterProvider`. The router is disposed with its Application Runtime Generation, and a new API identity receives fresh memory history. Workflow commands and transition events execute canonical absolute destinations through `WidgetNavigation` after their ownership and stale-result checks. Declarative route guards, route reads used only for presentation, and view-local tabs, breadcrumbs, and back controls remain React concerns. + +Keep one named React-only integration seam for RainbowKit modal commands. A provider adapter installs connect- and chain-modal commands into a runtime-scoped `WalletModal` interface and releases them with the provider lifetime. Feature state and commands do not carry modal callbacks. Remove the existing module-global latest-chain-modal callback mechanism. + +The completed migration is behavior-preserving. Classic and dashboard Earn, position details, transaction flows, activity, portfolio, deep links, loading, pagination, retry, KYC, analytics, and routing retain their visible semantics. The final tree contains no compatibility aggregate page-model adapter and no dual ownership of the migrated behavior. + +## User Stories + +1. As a widget user, I want the Earn page to preserve its current selections and interactions, so an internal architecture change does not alter how I stake. +2. As a widget user, I want classic and dashboard Earn variants to show the same token, yield, validator, amount, and provider information as before, so the two experiences remain consistent. +3. As a widget user, I want token searching to filter the available token options correctly, so I can quickly find the asset I intend to use. +4. As a widget user, I want yield searching, ranking, grouping, and category filtering to remain correct, so I can find an appropriate yield. +5. As a widget user, I want validator search requests to wait for the established debounce interval, so typing does not issue a request for every keystroke. +6. As a widget user, I want the UI to show that validator search is still debouncing, so loading feedback remains accurate. +7. As a widget user, I want obsolete validator-search results ignored after I change the query, so stale results cannot replace the active search. +8. As a widget user, I want loading more tokens to continue from the active token resource, so pagination remains coherent after selection or wallet changes. +9. As a widget user, I want loading more validators to target the active yield and normalized search, so pages cannot be appended to the wrong list. +10. As a widget user, I want accumulated pagination results preserved during later-page loading and retry, so lists do not disappear unnecessarily. +11. As a widget user, I want resource failures represented consistently, so I can distinguish loading, ready, empty, and recoverable failure states. +12. As a widget user, I want Retry to refresh the resource responsible for the current failure, so recovery does not reset unrelated state. +13. As a widget user, I want later refresh failures to retain usable prior data, so an auxiliary failure does not replace a coherent page with a blocking error. +14. As a widget user, I want my available amount, minimum, maximum, and force-max behavior to remain unchanged, so amount entry remains trustworthy. +15. As a widget user, I want amount validation to use one definition across Earn and position details, so the same Yield Entry is never accepted in one place and rejected in another. +16. As a widget user, I want provider selection and provider details to remain accurate, so the prepared Action Command uses the provider I selected. +17. As a widget user, I want reward estimates and reward-token details to remain accurate across Earn, position details, and review, so I can understand the expected result. +18. As a widget user, I want validator selection requirements to remain accurate, so a Yield Entry cannot proceed with missing or invalid validators. +19. As a widget user, I want KYC status and refresh behavior to remain consistent across Earn and position details, so eligibility is checked in the same way. +20. As a widget user, I want a blocked KYC gate to prevent submission without losing my form state, so I can resume after verification. +21. As a widget user, I want the generated Enter Action Command to retain address, token, validator, provider, subnet, Tron resource, Ledger, and max-amount arguments, so execution remains correct. +22. As a disconnected widget user, I want the Earn CTA to open the wallet connection modal, so I can connect and continue. +23. As a Ledger Live user, I want the add-account CTA to request and switch the account and close the chain modal, so Ledger account setup continues to work. +24. As a connected widget user, I want submitting a valid Yield Entry to start one fresh Classic Transaction Flow and navigate to Review, so the journey begins exactly once. +25. As a widget user, I want invalid submission to mark validation state without starting a transaction or navigating, so errors are visible and safe. +26. As a widget user, I want deep-link initialization to preserve its current readiness and selection behavior, so external links still land on the intended journey. +27. As a widget user, I want pending-action deep links to navigate only after their existing readiness conditions settle, so routes do not advance prematurely. +28. As a widget user, I want Classic Transaction Flow Continue, Back, and completion navigation to retain their existing history behavior, so Review, Steps, and Complete remain coherent. +29. As a widget user, I want Borrow Transaction Flow navigation to retain its existing session and completion behavior, so browser history cannot revive a disposed execution. +30. As a widget user, I want declarative route guards to keep redirecting invalid URLs safely, so missing or stale sessions do not render protected pages. +31. As a widget user, I want view-local tabs and back controls to behave as before, so ordinary interface navigation is not changed by workflow refactoring. +32. As a widget user, I want configured scroll-to-top behavior preserved on navigation that currently requests it, so page transitions retain their established presentation. +33. As a widget user, I want browser Back and replacement navigation to preserve their current semantics, so the history stack remains predictable. +34. As a widget user, I want stale Flow Sessions or Execution Attempts unable to navigate after replacement, so an obsolete workflow cannot take over the current screen. +35. As a widget user, I want analytics events for Max, Connect, Ledger account setup, submission, and workflow transitions to remain accurate, so product telemetry is not lost. +36. As a widget host, I want the React component export to remain compatible, so adopting this internal refactor requires no host changes. +37. As a widget host, I want the bundled renderer and its unmount/remount behavior to remain compatible, so imperative and CDN integrations continue working. +38. As a widget host, I want one memory router scoped to each Application Runtime Generation, so state from a prior API identity or sequential mount cannot leak into a later generation. +39. As a feature developer, I want stable capability view Atoms, so components subscribe only to the state they actually render. +40. As a feature developer, I want separate typed command Atoms, so UI events dispatch intent without carrying implementation callbacks. +41. As a feature developer, I want dynamic resource identity hidden inside the facade, so React never has to read one Atom to discover another Atom. +42. As a feature developer, I want pagination and retry exposed as semantic commands, so callers do not know which resource Atom is active. +43. As a feature developer, I want deterministic calculations implemented as pure TypeScript, so they are reusable and testable without ceremonial Atom wrappers. +44. As a feature developer, I want translation performed from semantic data in React, so application-logic modules remain React-free. +45. As a feature developer, I want local focus, disclosure, hover, animation, and element-reference state to remain in React, so the facade does not absorb presentation-only concerns. +46. As a feature developer, I want one `YieldSummary` interface for provider and reward projections, so activity, portfolio, review, completion, Earn, and position details stop importing Earn hooks. +47. As a feature developer, I want one `YieldEntry` interface for entry preparation and submission, so Earn and position details share the same behavior without depending on one another. +48. As a feature developer, I want each feature to supply its own input Atom to shared modules, so route, item, and Flow Session lifetimes remain owned by the caller. +49. As a feature developer, I want shared modules to consume Authoritative Resources rather than hook-owned fetches, so canonical remote read ownership remains intact. +50. As a feature developer, I want explicit normalized loading and failure states instead of nullable hook results, so callers do not infer why data is absent. +51. As a feature developer, I want application-owned navigation available as an Effect-backed runtime command, so workflow code does not publish outcomes for React to apply. +52. As a feature developer, I want navigation commands to accept canonical absolute destinations, so behavior is independent of React route context. +53. As a feature developer, I want router promises and failures owned by the runtime module, so event handlers never discard asynchronous navigation work. +54. As a feature developer, I want RainbowKit modal callbacks isolated in one named provider adapter, so third-party React constraints do not spread into feature state. +55. As a feature developer, I want command-related tracking performed with the command, so analytics follows the same ownership and stale-result checks as the behavior it records. +56. As a maintainer, I want the Earn machine to remain the sole source of Earn Selection and readiness, so a second writable page model cannot diverge from it. +57. As a maintainer, I want the aggregate Earn binding, aggregate page-model Atom, aggregate model type, and model hook removed, so the old architecture cannot remain as a compatibility layer. +58. As a maintainer, I want every consumer of the migrated shared hooks moved to feature or shared-module Atoms, so two architectures do not persist indefinitely. +59. As a maintainer, I want legacy calculation hooks deleted after migration, so ownership can be found from the module graph. +60. As a maintainer, I want unrelated surrounding feature logic left alone unless it blocks a headless dependency chain, so the migration remains bounded. +61. As a maintainer, I want dependency migration to stop at stable Atoms, Authoritative Resources, or immutable route/session input, so React values are not republished merely to satisfy a new facade. +62. As a maintainer, I want Classic and Borrow Flow scopes to retain their established ownership, so shared projections do not become application-global state. +63. As a maintainer, I want application navigation executed only from explicit commands or transition events, so reading a derived Atom never causes a side effect. +64. As a maintainer, I want route guards distinguished from workflow navigation commands, so authorization and route validity remain declarative. +65. As a maintainer, I want the module-global chain-modal callback holder removed, so sequential mounts and provider lifetimes do not depend on ambient mutable state. +66. As a maintainer, I want the final architecture to conform to the accepted facade and navigation ADRs, so code and documentation tell the same story. +67. As a test author, I want public facade and deep-module interfaces to be the primary seams, so tests survive internal file and helper refactors. +68. As a test author, I want to inject deterministic resource results, clocks, `WidgetNavigation` services, and WalletModal adapters, so asynchronous and external behavior can be tested without React orchestration. +69. As a test author, I want command branch tables for disconnected, connected, invalid, KYC-blocked, Ledger-placeholder, and valid Yield Entry states, so every submission decision is explicit. +70. As a test author, I want navigation integration tests to inspect the runtime-owned memory router, so Classic, Borrow, deep-link, and Yield Entry navigation can be verified without mounting React adapters. +71. As a test author, I want debounce tests to use deterministic time, so validator-search behavior is fast and reliable. +72. As a test author, I want existing browser journeys retained as end-to-end evidence, so internal refactoring cannot silently change user-visible behavior. +73. As a reviewer, I want deletion of old hooks, aggregate bindings, nested Atom view fields, and the router adapter bridge demonstrated in the final diff, so the migration proves replacement rather than layering. +74. As an AI coding agent, I want narrow named modules and stable public entries, so ownership and safe change surfaces are discoverable without tracing a monolithic React model. +75. As a widget host, I want ordinary live settings changes to preserve the current in-memory route, so configuration callbacks and presentation updates do not restart my journey. +76. As a widget host, I want an API-identity change to reset in-memory history with the rest of application state, so a fresh generation cannot open on a stale Review, Steps, or Details route. +77. As a feature developer, I want router construction and disposal owned by one scoped Layer, so React does not act as a dependency-injection bridge. +78. As a feature developer, I want React to obtain the runtime-owned router synchronously through an internal Atom, so `RouterProvider` needs no loading state, fallback router, or synchronization effect. +79. As a maintainer, I want `ApplicationRouter` to be the only service in the base runtime, so runtime composition stays explicit and minimal. +80. As a maintainer, I want `WidgetNavigation` constructed directly from `ApplicationRouter`, so a forwarding adapter does not duplicate the service boundary. +81. As a maintainer, I want the complete React Router instance restricted to top-level composition, so feature modules cannot bypass canonical navigation commands. +82. As a maintainer, I want the router explicitly disposed when its Application Runtime Generation closes, so pending navigation, loaders, blockers, and subscriptions cannot outlive their owner. + +## Implementation Decisions + +- ADR-0004 remains the base application-logic rule: Effect and Effect Atom own business state, asynchronous work, failure normalization, retries, concurrency, resource lifetimes, and command effects; React is a synchronous view adapter. +- ADR-0008 remains authoritative for canonical remote reads. Shared modules and feature facades derive from Authoritative Resources and do not create feature-owned remote caches or alternate retry and pagination policy. +- ADR-0009 remains authoritative for Earn Selection, Earn Readiness, failure precedence, initialization, and machine reconciliation. The new facade changes how that state is projected and consumed, not the machine's domain authority. +- ADR-0011 governs facade interfaces. Public view values contain no nested Atoms, Atom factories, or retry, refresh, pagination, or command callbacks. +- ADR-0012 governs application-owned navigation. An Application Runtime Generation owns the memory router and workflow decisions use the application-runtime navigation interface; React remains responsible for route guards and view-local navigation. +- The Earn machine remains the sole writable source of Earn Selection and entry-form intent. The facade derives capability views and forwards typed commands to the machine and active resources. +- Earn facade capabilities are organized by stable behavior rather than React component names. They cover token options, yield options, validators, amount and quote, yield summary, Yield Entry, submission and CTA, and page-level failure. +- The final Earn interface does not expose one aggregate page model. Consumers subscribe to the capability they render. +- Dynamic Authoritative Resource selection occurs inside derived or command Atoms. Token and validator Pull, loaded-validator memory, retry targets, and resource-family selection do not cross the facade. +- Token and yield search state is Atom-owned. Their filters are direct derived projections; React deferred values are removed unless later profiling establishes a separate presentation need. +- Validator search normalization and the established debounce interval are Atom-owned using Effect time. The active view exposes whether it is debouncing. +- Search, selection, pagination, loading, empty, and failure projections are deterministic and testable without React. +- Deterministic filtering, grouping, sorting, formatting, amount calculations, validation, reward calculations, provider projection, reward-token projection, yield-type projection, and Action Command construction remain pure TypeScript functions composed by Atoms. +- View Atoms expose semantic categories or translation keys rather than invoking React i18n. React performs translation and renders JSX without domain branching. +- Locale-independent deterministic formatting may occur in pure projections. Locale and translation-dependent presentation remains at the view seam. +- Synchronous local presentation state with no workflow meaning, persistence, asynchronous behavior, route lifetime, or cross-view coordination remains in React. +- `YieldSummary` is a top-level feature module with a narrow public entry. It accepts feature-owned input through an Atom and exposes a stable read-only view Atom. +- `YieldSummary` owns provider-yield lookup composition, provider details, reward-token details, semantic yield type, and normalized loading, ready, and failed states. +- `YieldSummary` does not own translation, route state, selection intent, transaction preparation, or feature-specific layout. +- `YieldEntry` is a top-level feature module with a narrow public entry. It accepts a feature-owned input Atom and exposes stable view and command Atoms. +- `YieldEntry` owns entry amount constraints, force-max projection, amount validation, KYC projection and refresh, estimated rewards, Enter Action Command construction, submission eligibility, CTA decisions, command-related analytics, transaction-session start, and application navigation. +- `YieldEntry` composes `YieldSummary` rather than duplicating its provider and reward projections. +- A feature input supplies selected yield, selected token, selected validators, selected provider, amount intent, relevant positions and balances, Wallet Scope facts, required configuration, and destination policy. Inputs express domain policy rather than caller names such as classic or dashboard. +- Earn and dashboard position-details entry compose `YieldEntry` with their own state and lifetime. The shared module does not import either caller. +- Classic position-details exit behavior may reuse extracted amount calculations without being forced into the entry module. +- Classic Transaction Flow review and completion compose `YieldSummary` inside their existing Session, Review, and Execution scopes. +- Activity and portfolio item projections use item-keyed feature Atoms and `YieldSummary`; they do not become global Earn state. +- When a required input is still React-hook-owned, its dependency chain is migrated only until it reaches a stable Atom, Authoritative Resource, or immutable route/session input. A React effect must not republish changing hook state into the new module. +- All current consumers of estimated rewards, amount limits, amount validation, provider details, reward-token details, yield KYC, yield type, stake-enter request construction, and pending-action deep-link adaptation are migrated. +- Legacy shared hooks are deleted when their final consumer moves. Thin wrappers are not retained in the finished change merely for compatibility. +- The aggregate Earn binding, aggregate page-model Atom, aggregate model type, and aggregate hook are deleted after classic and dashboard consumers migrate. +- The facade does not require a React provider or root binding. Derived capabilities live in the existing Widget Instance Atom registry, and consumers mount only what they read. +- Lifecycle Atoms are introduced only when an effectful resource genuinely follows route or view visibility. They do not assemble or publish aggregate view models. +- `WidgetNavigation` is a headless Effect module in the application runtime. Feature and workflow code depends on its narrow command interface, never on the React Router instance. +- A separate synchronous Atom runtime contains only `ApplicationRouter`. Its static scoped Layer creates the existing memory router and registers deterministic router disposal. +- The internal router Atom uses synchronous service projection. Router construction is treated as an invariant and does not add loading, retry, fallback-router, or recoverable failure UI. +- The React composition boundary reads the router Atom and passes the value to the DOM `RouterProvider`. React does not construct, retain, synchronize, or dispose the router. +- The complete router service remains internal to runtime composition. Feature modules receive `WidgetNavigation`, not the raw React Router instance. +- The Application Router runtime provides its Effect Context to the application runtime, following the existing application-runtime-to-wallet-runtime composition pattern. +- `WidgetNavigation` is constructed directly from `ApplicationRouter`. The production and test navigation adapter types, adapter Atom, registry-provider adapter input, and React forwarding object are removed. +- Tests of feature and workflow behavior provide `WidgetNavigation` directly. Focused integration tests use the real runtime-owned memory router. +- Application navigation commands use canonical absolute widget paths. Route helpers own destination construction. +- Navigation distinguishes push, replace, and back operations and carries a semantic scroll-reset or preserve policy. `WidgetNavigation` retains the existing widget configuration that can disable automatic scroll reset. +- The static root route configuration is assembled at the top-level React composition seam and supplied to the Application Router Layer. Application/service modules do not import React. +- The router remains a memory router. Browser and hash routers are not introduced. +- API-identity replacement closes the current Application Runtime Generation and creates fresh memory history. Live settings changes that retain the generation preserve the router. +- `RouterProvider` is imported from the documented DOM entrypoint. Router creation and route APIs continue to use the base React Router entrypoint. +- Workflow modules execute navigation only from explicit commands or transition events after checking current Flow Session, Execution Attempt, or intent ownership. +- Derived view Atoms never navigate when read, mounted, refreshed, or recomputed. +- Pending-action deep-link routing invokes `WidgetNavigation` directly once readiness and intent-claim rules pass; its React outcome bridge and delivery state are removed. +- Classic Transaction Flow forward, cancellation, and completion navigation invokes `WidgetNavigation` from its scoped commands or transition events. Existing stale-session suppression remains. +- Borrow Transaction Flow base, forward, and completion navigation invokes `WidgetNavigation` from its scoped commands or transition events. Existing epoch and execution checks remain. +- Declarative redirects that guard route validity remain React `` behavior. +- Tabs, breadcrumbs, view-local back controls, and external URL navigation remain outside `WidgetNavigation` unless separately redesigned. +- RainbowKit modal integration remains the one required React-only command seam for this effort. +- A named provider adapter installs connect- and chain-modal commands into a runtime-scoped `WalletModal` port and releases them on provider teardown. +- `WalletModal` callbacks are private adapter implementation. They do not appear in facade views, Yield Entry inputs, or command payloads. +- Ledger add-account behavior depends on `WalletModal` through the runtime and no longer accepts a close-modal callback as command data. +- The module-global latest-chain-modal callback holder is removed. +- Analytics caused by commands runs in the owning Atom/Effect command through the tracking runtime. Page-impression analytics may remain a route visibility adapter. +- The migration preserves public React and bundled-renderer interfaces. +- The migration preserves existing user-facing copy; no translation resource changes are required unless implementation reveals an accidental behavior dependency. +- The migration preserves single-Widget-Instance and sequential-remount constraints and introduces no machinery for concurrent Widget Instances. +- The final change contains one authority for each migrated behavior. Temporary compatibility used between local implementation stages is deleted before completion. +- Implementation proceeds in green stages: pure projections, shared resource selectors, runtime navigation and modal seams, shared modules, Earn facade and consumers, position details, transaction flows, activity and portfolio, legacy deletion, documentation alignment, and full verification. +- Existing unrelated React Query, direct `useNavigate`, and legacy hooks are not migrated unless they are in the required dependency chain or implement an Atom-generated navigation outcome covered by this spec. + +## Testing Decisions + +- Tests assert observable behavior through the highest stable interface: a feature facade, `YieldEntry`, `YieldSummary`, or `WidgetNavigation`. Tests do not inspect private mutable Atoms, private resource selection, helper call counts, React memoization, or file organization. +- Pure calculation tests cover filtering, grouping, sorting, amount limits, force-max behavior, validation, provider projection, reward projection, reward-token projection, semantic yield type, CTA projection, and Enter Action Command construction. +- `YieldSummary` interface tests use controllable Authoritative Resource results and assert normalized loading, ready, failed, refresh-with-value, provider, reward-token, and semantic type views. +- `YieldEntry` interface tests use controllable input Atoms and injected adapters. They assert amount constraints, KYC states and refresh, rewards, validation, CTA states, and command effects. +- Yield Entry command tests use a decision table covering disconnected, external-provider-hidden, connecting, invalid, submitted-invalid, missing request input, KYC-blocked, Ledger-placeholder, valid connected, and stale-owner cases. +- Yield Entry command tests assert transaction-session start, analytics, WalletModal commands, navigation commands, and non-occurrence of forbidden effects. +- Earn facade registry tests assert capability projections and commands without mounting React. Existing Earn machine registry tests remain the prior art for controlled machine, resource, and race behavior. +- Search tests use deterministic Effect time to assert normalization, the established validator debounce interval, the debouncing flag, query-key changes, and stale-result suppression. +- Pagination tests assert that stable load-more commands route to the active token or validator resource, ignore duplicate pulls while waiting, preserve accumulated values, and do not expose resource Atoms to callers. +- Retry tests assert that stable retry commands refresh the current responsible resource and do not retain a stale resource identity after selection or key changes. +- `WidgetNavigation` capability tests assert push, replace, back, absolute destinations, scroll policy, successful completion, and normalized failure without depending on React. +- Application Router integration tests assert that the first Atom read synchronously returns the memory router, real commands update its route state, one runtime generation preserves router identity, API-identity replacement creates fresh history, and registry disposal disposes the router exactly once. +- Pending-action deep-link registry tests assert readiness gating, intent claiming, Classic Flow start where required, and direct navigation without a mounted React adapter. +- Classic Flow facade tests assert Review-to-Steps, execution cancellation, completion, current-session checks, browser-history semantics, and suppression of stale navigation. +- Borrow Flow facade tests assert Base, Steps, Complete, session epoch checks, execution lifetime, and suppression of stale navigation. +- Route-level DOM/browser tests continue asserting declarative invalid-session guards because those guards intentionally remain React-owned. +- WalletModal adapter tests assert provider acquisition, replacement, cleanup, and unavailability behavior. Feature tests inject a fake port rather than mounting RainbowKit. +- Ledger account tests assert that successful account switching closes the chain modal through the port and that missing or invalid Ledger connectors retain typed failure behavior. +- Consumer migration tests replace mocked aggregate page-model hooks with Atom registry inputs or public module fakes. +- Existing provider-selection, validator-selection, Earn workflow, position-details, Classic Flow, Borrow Flow, deep-link, and staking browser tests are retained as behavior-level prior art. +- Browser tests cover at least one classic Earn journey and one dashboard/position-details Yield Entry journey through Review, proving that the shared module and runtime navigation integrate with real UI adapters. +- Regression tests assert that mounting and unmounting a Widget Instance releases modal adapters, router/runtime work, and scoped flow modules before sequential remount. +- Static verification rejects React-owned data-router construction, the removed navigation-adapter bridge, and raw router imports from feature modules. +- Static verification asserts no remaining imports of deleted shared hooks, aggregate Earn model interfaces, nested operational Atom fields in public view types, or React adapters for Atom-generated navigation outcomes. +- Lint and type checking are required after every materially complete stage. Focused unit and DOM tests run during slices; changed browser tests run for affected slices; full package verification runs before completion. +- Tests added for the new deep modules replace tests that only exercised deleted shallow hooks. Existing machine, schema, Authoritative Resource, and workflow tests remain when they still describe public behavior. +- A good test should survive moving helpers, splitting files, or changing private Atom composition. If a test changes solely because implementation internals move while interface behavior is unchanged, it is testing below the intended seam. + +## Out of Scope + +- Changing visible Earn, position-details, activity, portfolio, review, completion, KYC, or transaction-flow product behavior. +- Redesigning the UI, adding new controls, changing copy, or changing translations. +- Changing backend API contracts, generated schemas, Action Command semantics, wallet protocols, or transaction execution mechanics. +- Replacing or redesigning the existing Earn machine's selection, readiness, initialization, reconciliation, and failure precedence. +- Rewriting every surrounding activity, portfolio, position-details, or transaction-flow model. Only migrated capabilities and dependencies required to make them headless are included. +- Migrating every direct React Router `useNavigate` call. Declarative guards and view-local navigation remain React-owned. +- Converting Classic or Dashboard declarative routes into data-router route objects. +- Replacing the memory router with a browser, hash, or host-URL router. +- Mirroring route state into Atom before application logic has a concrete route-read requirement. +- Moving external URL navigation into the widget navigation module. +- Replacing RainbowKit or modifying its package interface. +- Migrating unrelated React Query resources or hook-owned logic outside the required dependency chains. +- Adding concurrency support for more than one mounted Widget Instance per browser document. +- Adding a second Atom registry, ad hoc Effect runtime, global router singleton, global navigation queue, or global normalized feature-state cache. +- Retaining a permanent compatibility aggregate page-model hook, callback-rich facade, or dual shared-hook implementation. +- Creating new module-specific architecture documents or storing the temporary implementation checklist in permanent architecture documentation. +- Prototyping alternative UI or state behavior; the design questions were resolved in conversation and ADRs. + +## Further Notes + +- The canonical term **Yield Entry** is defined in the project glossary. Avoid “Enter Action” for the pre-execution attempt because **Yield Action** refers to the created action and **Action Command** refers to its prepared instruction. +- `YieldSummary`, `WidgetNavigation`, facade, and port are implementation vocabulary and do not belong in the domain glossary. +- **Application Runtime Generation** is the canonical lifecycle term for application state under one stable API identity. Avoid the ambiguous term “Widget Runtime.” +- The accepted facade ADR supersedes only the part of the Earn-state ADR that allowed operational pagination and retry-target Atoms to cross the published view. The rest of the Earn-state decision remains authoritative. +- The accepted navigation ADR replaces the earlier living-architecture rule that React route adapters apply Atom navigation outcomes. +- Existing Classic and Borrow architecture documents and the widget-wide architecture document have already been aligned with the accepted target. +- The migration is complete across all eight tracer-bullet issues, including + Application Runtime Generation ownership of the memory router. +- No runnable prototype is required before ticketing. The principal risks are migration breadth, lifecycle preservation, and behavioral regression, all of which have established registry and browser test seams. diff --git a/.scratch/authoritative-resources/issues/01-share-yield-positions.md b/.scratch/authoritative-resources/issues/01-share-yield-positions.md new file mode 100644 index 000000000..a58fa3dd2 --- /dev/null +++ b/.scratch/authoritative-resources/issues/01-share-yield-positions.md @@ -0,0 +1,16 @@ +# 01 — Share Yield positions through the first Authoritative Resource + +**What to build:** Establish the Authoritative Resource pattern through a complete Yield positions slice. Earn and Portfolio must read one canonical Wallet Scope-keyed position fact, share one acquisition and cache policy, and keep historical positions available before feature visibility projections. + +**Blocked by:** None — can start immediately. + +**Status:** implemented + +- [x] A narrow `YieldResourceSource` capability provides the aggregate-position read without exposing the broad Yield backend service. +- [x] One named Yield Positions resource owns explicit request identity, acquisition, typed failures, freshness, retry, stale-result suppression, and semantic position invalidation. +- [x] Equivalent Earn and Portfolio requests within one Widget Instance share one acquisition and cached result. +- [x] Earn and Portfolio consume canonical positions through read-only projections and no replaced feature-local fetch owner remains. +- [x] Portfolio totals and grouping retain historical positions even when their yields are not currently selected or visible. +- [x] Resource-contract tests cover exact request sharing, distinct Wallet Scopes, typed failure, invalidation, and registry lifetime. +- [x] A generated-client adapter test proves aggregate-position request and response mapping at the capability seam. +- [x] Focused feature tests and widget lint/type checking pass. diff --git a/.scratch/authoritative-resources/issues/02-share-token-balance-scans.md b/.scratch/authoritative-resources/issues/02-share-token-balance-scans.md new file mode 100644 index 000000000..948705924 --- /dev/null +++ b/.scratch/authoritative-resources/issues/02-share-token-balance-scans.md @@ -0,0 +1,15 @@ +# 02 — Share wallet token-balance scans + +**What to build:** Replace the separate Earn and Portfolio token scans with one canonical Token Balances resource keyed by complete Wallet Scope and token-scan identity, while preserving each feature's presentation model. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource. + +**Status:** implemented + +- [x] A narrow `LegacyResourceSource` capability exposes the semantically read-only token scan without exposing the broad Legacy backend service. +- [x] One Token Balances resource owns canonical input normalization, empty-input behavior, typed failures, freshness, retry, interruption, and wallet-balance invalidation. +- [x] Equivalent Earn and Portfolio scans share one backend acquisition and canonical result. +- [x] Feature-specific token models are pure projections and cannot create a second request authority. +- [x] Complete Wallet Scope identity prevents results from one wallet or network appearing in another. +- [x] Contract tests cover sharing, empty inputs, duplicate identifiers, distinct scopes, invalidation, and Widget Instance remount. +- [x] Adapter, affected feature, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/03-share-yield-opportunity-provider-facts.md b/.scratch/authoritative-resources/issues/03-share-yield-opportunity-provider-facts.md new file mode 100644 index 000000000..03ac9fce9 --- /dev/null +++ b/.scratch/authoritative-resources/issues/03-share-yield-opportunity-provider-facts.md @@ -0,0 +1,15 @@ +# 03 — Share Yield opportunity and provider facts + +**What to build:** Give individual Yield opportunities and providers one authoritative cache owner so ordinary lookup, initialization, details, and enrichment consumers reuse the same canonical decoded facts. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource. + +**Status:** implemented + +- [x] Ordinary and initial Yield lookup paths use one request identity when they represent the same remote fact. +- [x] Provider lookups are named resources with explicit provider identity and typed missing-provider behavior. +- [x] Initialization and feature-specific interpretation remain projections outside canonical resource storage. +- [x] Equivalent concurrent and sequential consumers share acquisition, fresh state, retry, and failure state. +- [x] Raw generated-client response types and errors do not cross either resource interface. +- [x] Replaced feature-local Yield and provider fetch owners are removed. +- [x] Contract, adapter, feature integration, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/04-share-complete-yield-directory.md b/.scratch/authoritative-resources/issues/04-share-complete-yield-directory.md new file mode 100644 index 000000000..c5665c1df --- /dev/null +++ b/.scratch/authoritative-resources/issues/04-share-complete-yield-directory.md @@ -0,0 +1,16 @@ +# 04 — Share bounded Yield Directory reads + +**What to build:** Replace separate Yield-list fetchers with a canonical directory that completely resolves explicit requested Yield IDs, plus a shared bounded category-summary resource that performs one maximum-size request per category. Provider enrichment remains a projection over the explicit-ID directory. + +**Blocked by:** 03 — Share Yield opportunity and provider facts. + +**Status:** implemented + +- [x] Endpoint-equivalent listing requests resolve through one named resource with complete explicit filter and sort identity. +- [x] Full-result consumers receive all applicable pages rather than an arbitrary first page. +- [x] Available categories use one `offset: 0`, API-maximum-size request per category and do not scan the complete unfiltered Yield catalog. +- [x] Provider enrichment reuses the provider resource, deduplicates provider identities, and has explicit failure and missing-provider semantics. +- [x] Availability, selected, visible, and token-scope behavior remains downstream projection logic. +- [x] No former listing or category atom retains independent acquisition or freshness policy. +- [x] Tests cover equivalent and distinct requests, explicit-ID pagination boundaries, bounded category requests, provider deduplication, failures, and stale-result suppression. +- [x] Adapter, feature integration, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/05-share-earn-token-discovery.md b/.scratch/authoritative-resources/issues/05-share-earn-token-discovery.md new file mode 100644 index 000000000..f382394db --- /dev/null +++ b/.scratch/authoritative-resources/issues/05-share-earn-token-discovery.md @@ -0,0 +1,21 @@ +# 05 — Share Earn token discovery + +**What to build:** Restore the two intentional token-discovery contracts: Yield API results use one shared demand-driven Pull per semantic query, while Legacy token options remain a complete non-paginated resource. The feature chooses the source and projects either result without owning transport pagination. + +**Blocked by:** 02 — Share wallet token-balance scans. + +**Status:** implemented + +- [x] Yield token discovery has complete network and Yield-type identity, requests only the first backend page initially, and advances by one backend continuation per accepted Pull. +- [x] Equivalent Yield token consumers share one Pull Atom and accumulated progress; the feature facade does not create a second stream or paginate a complete array in memory. +- [x] Legacy token options use `LegacyResourceSource` and preserve network-specific behavior. +- [x] Legacy token options remain a complete non-paginated resource and are exposed as immediately done, with no synthetic pages or fake continuation. +- [x] Empty network or filter inputs avoid meaningless I/O where the semantic result is empty. +- [x] Canonical token facts remain independent from selected-token and view presentation state. +- [x] Former feature-local token-option acquisition paths are removed. +- [x] Tests prove one initial Yield page, one request per Pull, shared progress, backend-derived continuation, refresh from page one, and complete Legacy behavior. +- [x] Focused adapter and token-selection tests, lint, and type-check validation pass. + +## Comments + +- Pagination audit against `77802a3c2849416602a0b20280a5b40acb7b6cf1` found that the Yield branch was incrementally paginated while the Legacy branch intentionally returned a complete list. Eagerly collecting all Yield pages was a regression. diff --git a/.scratch/authoritative-resources/issues/06-share-validator-discovery.md b/.scratch/authoritative-resources/issues/06-share-validator-discovery.md new file mode 100644 index 000000000..95b26ac9d --- /dev/null +++ b/.scratch/authoritative-resources/issues/06-share-validator-discovery.md @@ -0,0 +1,22 @@ +# 06 — Share validator discovery + +**What to build:** Restore the validator endpoint's distinct semantic contracts: ordinary and search discovery use shared demand-driven Pull resources, preferred validators use an explicit complete resource, and address resolution uses a bounded point resource. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource. + +**Status:** implemented + +- [x] Each validator contract has a minimal complete identity: discovery includes Yield, status, and search inputs; preferred includes its applicable scope; address lookup includes Yield and address. Transport continuation is never part of caller identity. +- [x] Ordinary and search discovery request only the first backend page initially and advance by one backend continuation per accepted Pull. +- [x] Equivalent ordinary or search consumers share the same Pull Atom and accumulated progress; feature projections forward Pull and Refresh instead of creating another stream. +- [x] Validator search advances its name and address branches independently and concurrently, then merges and deduplicates their emitted batches deterministically. +- [x] Preferred validators acquire the complete applicable result explicitly; address resolution remains a bounded point lookup and neither contract is routed through the ordinary Pull. +- [x] Continuation, later-page failure, waiting, and refresh use native Atom and Stream behavior without page caches, manual offsets, locks, or custom refresh coordination. +- [x] Feature code receives semantic validator state rather than raw offsets or generated-client page types. +- [x] Replaced validator fetch implementations are removed. +- [x] Tests prove incremental ordinary/search acquisition, shared progress, independent search continuations, complete preferred acquisition, bounded address lookup, and refresh from page one. +- [x] Focused resource and UI integration tests, lint, and type-check validation pass. + +## Comments + +- Pagination audit against `77802a3c2849416602a0b20280a5b40acb7b6cf1` found that the endpoint intentionally served incremental, complete, and point consumers. Replacing those contracts with one eager complete directory was a regression. diff --git a/.scratch/authoritative-resources/issues/07-share-activity-history.md b/.scratch/authoritative-resources/issues/07-share-activity-history.md new file mode 100644 index 000000000..2b2a05c13 --- /dev/null +++ b/.scratch/authoritative-resources/issues/07-share-activity-history.md @@ -0,0 +1,24 @@ +# 07 — Share Activity history + +**What to build:** Give Activity history one shared demand-driven Pull owner. It emits semantic batches containing actions and the backend total; Activity filter counts use bounded summary requests, and feature enrichment projects the canonical Pull without introducing another pagination stream. + +**Blocked by:** 03 — Share Yield opportunity and provider facts. + +**Status:** implemented + +- [x] Activity request identity includes the semantic wallet owner scope, filters, and ordering; backend offset and continuation remain private stream state rather than caller-provided cache identity. +- [x] Initial Activity acquisition requests only the first backend page and each accepted Pull advances by one backend continuation derived from the response's offset, limit, and total. +- [x] Equivalent Activity consumers share one Pull Atom and accumulated progress; no complete-history resource or in-memory pagination remains. +- [x] Pull emissions carry both the action batch and backend total, replacing pagination side atoms. +- [x] Activity filter counts use bounded summary requests that read backend totals without collecting history. +- [x] Yield and validator enrichment reuse authoritative resources as a projection over the Activity Pull and do not create duplicate lookups or a second pagination stream. +- [x] Activity invalidation affects all relevant variants without eagerly fetching inactive variants. +- [x] Obsolete or disposed requests cannot publish into a newer Activity state. +- [x] Existing Activity display and Activity Resume behavior remain compatible. +- [x] Later-page failure preserves accumulated actions, waiting prevents repeated Pull dispatch, and Refresh restarts from page one using native Atom and Stream behavior. +- [x] Tests prove one initial request, one request per Pull, shared progress, bounded counts, backend-derived continuation, refresh behavior, and absence of eager full-history acquisition. +- [x] Focused contract, integration, and invalidation tests, lint, and type-check validation pass. + +## Comments + +- Pagination audit against `77802a3c2849416602a0b20280a5b40acb7b6cf1` found that Activity history was product-level incremental pagination and counts were bounded total probes. Eagerly collecting all history and slicing it in memory was a regression. diff --git a/.scratch/authoritative-resources/issues/08-share-flow-balance-facts.md b/.scratch/authoritative-resources/issues/08-share-flow-balance-facts.md new file mode 100644 index 000000000..047815f74 --- /dev/null +++ b/.scratch/authoritative-resources/issues/08-share-flow-balance-facts.md @@ -0,0 +1,15 @@ +# 08 — Share flow balance facts + +**What to build:** Make single-Yield balances and gas-token balance checks authoritative facts reused by deep links, Review, position details, and completion views without moving workflow ownership into Resources. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource; 02 — Share wallet token-balance scans. + +**Status:** implemented + +- [x] Single-Yield balances use complete Yield and wallet identity and one resource-owned freshness and invalidation policy. +- [x] Gas-token balance requests use explicit Action Command and Wallet Scope-derived identity without reading flow state internally. +- [x] Classic Transaction Flow remains the owner of Action Preview and execution; Resources own only the cacheable balance facts. +- [x] Existing deep-link, Review warning, position-detail, and completion behavior consumes read-only projections. +- [x] Replaced feature and flow-local fetch owners are removed. +- [x] Tests cover sharing across consumers, distinct commands and wallets, empty inputs, failures, invalidation, and execution-scope disposal. +- [x] Adapter, flow integration, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/09-share-prices-reward-summaries.md b/.scratch/authoritative-resources/issues/09-share-prices-reward-summaries.md new file mode 100644 index 000000000..85d1b40d5 --- /dev/null +++ b/.scratch/authoritative-resources/issues/09-share-prices-reward-summaries.md @@ -0,0 +1,15 @@ +# 09 — Share prices and reward summaries + +**What to build:** Give token prices and per-Yield reward summaries authoritative owners so Earn, Portfolio, and position details share canonical financial facts and bounded backend work. + +**Blocked by:** 02 — Share wallet token-balance scans. + +**Status:** implemented + +- [x] Price request identity is complete and semantically equivalent token requests share one acquisition. +- [x] Empty and duplicate token inputs avoid unnecessary backend work and preserve deterministic result ordering. +- [x] Reward-summary requests canonicalize Yield identities safely, apply bounded concurrency, and represent missing summaries explicitly. +- [x] Feature totals and formatted values remain downstream projections over canonical facts. +- [x] Typed transport, decode, partial, and invariant failures do not leak raw adapter errors. +- [x] Former price and reward fetch owners are removed. +- [x] Contract, feature integration, adapter, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/10-share-kyc-yield-history-insights.md b/.scratch/authoritative-resources/issues/10-share-kyc-yield-history-insights.md new file mode 100644 index 000000000..1155e8d33 --- /dev/null +++ b/.scratch/authoritative-resources/issues/10-share-kyc-yield-history-insights.md @@ -0,0 +1,15 @@ +# 10 — Share KYC and Yield history insights + +**What to build:** Make KYC status, reward-rate history, and TVL history named authoritative resources with complete identity and consistent freshness across classic and dashboard consumers. + +**Blocked by:** 03 — Share Yield opportunity and provider facts. + +**Status:** implemented + +- [x] KYC identity includes Yield and wallet address and cannot reuse status across owners. +- [x] Reward-rate and TVL histories include Yield, period, and interval in their resource identities. +- [x] Each resource owns typed failures, freshness, retry, interruption, and stale-result behavior. +- [x] History sorting and chart formatting remain feature projections rather than cached feature models. +- [x] Existing KYC gates and insight displays retain their behavior. +- [x] Former insight fetch owners are removed. +- [x] Contract, dashboard and classic integration, adapter, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/11-share-widget-health-status.md b/.scratch/authoritative-resources/issues/11-share-widget-health-status.md new file mode 100644 index 000000000..3e12386ba --- /dev/null +++ b/.scratch/authoritative-resources/issues/11-share-widget-health-status.md @@ -0,0 +1,14 @@ +# 11 — Share widget health status + +**What to build:** Give backend health one authoritative resource owner so maintenance detection observes one typed, policy-controlled fact rather than calling the broad Yield backend service directly. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource. + +**Status:** implemented + +- [x] Health acquisition uses `YieldResourceSource` and exposes a stable typed resource state. +- [x] Polling, freshness, retry, and stale-result behavior are owned by the Health resource. +- [x] Maintenance presentation consumes a zero-logic read-only adapter. +- [x] The former direct health-service call is removed. +- [x] Tests cover healthy, maintenance, transport failure, retry, polling lifecycle, and Widget Instance disposal. +- [x] Focused integration, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/12-narrow-enabled-network-bootstrap.md b/.scratch/authoritative-resources/issues/12-narrow-enabled-network-bootstrap.md new file mode 100644 index 000000000..925acd1ad --- /dev/null +++ b/.scratch/authoritative-resources/issues/12-narrow-enabled-network-bootstrap.md @@ -0,0 +1,14 @@ +# 12 — Move enabled-network bootstrap behind the Legacy read capability + +**What to build:** Preserve Wallet Bootstrap network discovery while removing its dependency on the broad Legacy backend service and keeping bootstrap lifetime and failure behavior unchanged. + +**Blocked by:** 02 — Share wallet token-balance scans. + +**Status:** implemented + +- [x] Wallet Bootstrap obtains enabled networks through `LegacyResourceSource` with no broad-service dependency. +- [x] The capability exposes only the query required by bootstrap and returns canonical decoded network data. +- [x] Bootstrap Snapshot, readiness, fallback, and failure semantics remain unchanged. +- [x] No React adapter or feature atom becomes the owner of enabled-network acquisition. +- [x] Service and Wallet Bootstrap tests cover success, failure, runtime replacement, and sequential remount. +- [x] Lint and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/13-route-classic-operations.md b/.scratch/authoritative-resources/issues/13-route-classic-operations.md new file mode 100644 index 000000000..80cc5aab8 --- /dev/null +++ b/.scratch/authoritative-resources/issues/13-route-classic-operations.md @@ -0,0 +1,15 @@ +# 13 — Route classic transaction operations through YieldOperations + +**What to build:** Replace broad Yield backend access in Classic Transaction Flow and Transaction Workflow with a narrow operation capability covering Action Preview, submission, and status polling. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource. + +**Status:** implemented + +- [x] `YieldOperations` exposes only the operation and workflow calls required by classic transaction intent owners. +- [x] Action Preview remains owned by the Review scope and transaction status remains owned by Transaction Workflow rather than becoming shared cache resources. +- [x] Submission and polling preserve their typed errors, retries, interruption, and scoped lifetime. +- [x] Successful operations publish semantic position, balance, and activity invalidation at the correct completion points. +- [x] Feature commands remain synchronous Atom dispatch boundaries and do not call an Effect runtime directly. +- [x] Classic flow and workflow code no longer imports the broad Yield backend service. +- [x] Operation adapter, flow, workflow, invalidation, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/14-share-borrow-catalogs-positions.md b/.scratch/authoritative-resources/issues/14-share-borrow-catalogs-positions.md new file mode 100644 index 000000000..6dc4441cc --- /dev/null +++ b/.scratch/authoritative-resources/issues/14-share-borrow-catalogs-positions.md @@ -0,0 +1,15 @@ +# 14 — Share Borrow catalogs and positions + +**What to build:** Introduce `BorrowResourceSource` and authoritative resources for integrations, markets, and Wallet Scope positions so all Borrow and Portfolio views share canonical catalog and position facts. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource. + +**Status:** implemented + +- [x] Borrow integration, market, and position reads are exposed through a narrow read-source capability. +- [x] Market pagination and position fan-out are hidden behind resource-owned policies with bounded concurrency. +- [x] Complete network, integration, and Wallet Scope identities prevent incorrect cache sharing. +- [x] Borrow and Portfolio consumers share resource state and retain existing projections and empty states. +- [x] Borrow-market and Borrow-position invalidations affect all relevant variants. +- [x] Former Borrow resource atoms no longer acquire through the broad backend service. +- [x] Contract, adapter, feature integration, invalidation, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/15-route-borrow-operations.md b/.scratch/authoritative-resources/issues/15-route-borrow-operations.md new file mode 100644 index 000000000..5dadf3919 --- /dev/null +++ b/.scratch/authoritative-resources/issues/15-route-borrow-operations.md @@ -0,0 +1,14 @@ +# 15 — Route Borrow workflows through BorrowOperations + +**What to build:** Replace broad Borrow backend access in Borrow Transaction Flow and Transaction Workflow with an operation capability for action creation, advancement, polling, and submission. + +**Blocked by:** 14 — Share Borrow catalogs and positions. + +**Status:** implemented + +- [x] `BorrowOperations` exposes only the commands and workflow queries required by Borrow intent owners. +- [x] Action creation remains owned by Borrow Review and status polling remains owned by Transaction Workflow. +- [x] Existing missing-configuration, action, transaction, retry, and interruption behavior remains typed and scoped. +- [x] Successful Borrow operations invalidate affected positions and markets without enumerating cache keys. +- [x] Borrow feature and workflow modules no longer import the broad Borrow backend service. +- [x] Operation adapter, transaction-flow, workflow, invalidation, lint, and type-check validation pass. diff --git a/.scratch/authoritative-resources/issues/16-contract-yield-backend-service.md b/.scratch/authoritative-resources/issues/16-contract-yield-backend-service.md new file mode 100644 index 000000000..72cc3f060 --- /dev/null +++ b/.scratch/authoritative-resources/issues/16-contract-yield-backend-service.md @@ -0,0 +1,14 @@ +# 16 — Contract the broad Yield backend service + +**What to build:** Remove the generated-client-shaped Yield service and its application-runtime wiring after every Yield fact and operation has moved to an Authoritative Resource or `YieldOperations`. + +**Blocked by:** 01 — Share Yield positions through the first Authoritative Resource; 03 — Share Yield opportunity and provider facts; 04 — Share the complete Yield Directory; 06 — Share validator discovery; 07 — Share Activity history; 08 — Share flow balance facts; 10 — Share KYC and Yield history insights; 11 — Share widget health status; 13 — Route classic transaction operations through YieldOperations. + +**Status:** implemented + +- [x] No production caller imports or resolves the broad Yield backend service. +- [x] Runtime composition provides the Yield read-source and operation capabilities from one private generated-client adapter layer. +- [x] Generated Yield client types remain private to transport and adapter infrastructure. +- [x] The broad service contract, constructor, layer, and dead compatibility helpers are removed. +- [x] Yield resource and operation contract suites pass against the final runtime composition. +- [x] Widget lint, type checking, focused suites, and hygiene checks pass. diff --git a/.scratch/authoritative-resources/issues/17-contract-legacy-backend-service.md b/.scratch/authoritative-resources/issues/17-contract-legacy-backend-service.md new file mode 100644 index 000000000..3810bf6c3 --- /dev/null +++ b/.scratch/authoritative-resources/issues/17-contract-legacy-backend-service.md @@ -0,0 +1,14 @@ +# 17 — Contract the broad Legacy backend service + +**What to build:** Remove the broad Legacy service after every scan, token, price, reward, gas-balance, and bootstrap query uses `LegacyResourceSource`. + +**Blocked by:** 02 — Share wallet token-balance scans; 05 — Share Earn token discovery; 08 — Share flow balance facts; 09 — Share prices and reward summaries; 12 — Move enabled-network bootstrap behind the Legacy read capability. + +**Status:** implemented + +- [x] No production caller imports or resolves the broad Legacy backend service. +- [x] Runtime composition provides one narrow Legacy read-source capability backed by the private generated client. +- [x] Generated Legacy client types remain private to transport and adapter infrastructure. +- [x] The broad service contract, constructor, layer, and duplicated response adaptations are removed. +- [x] Legacy-backed resource and Wallet Bootstrap tests pass against final composition. +- [x] Widget lint, type checking, focused suites, and hygiene checks pass. diff --git a/.scratch/authoritative-resources/issues/18-contract-borrow-backend-service.md b/.scratch/authoritative-resources/issues/18-contract-borrow-backend-service.md new file mode 100644 index 000000000..b26ed462b --- /dev/null +++ b/.scratch/authoritative-resources/issues/18-contract-borrow-backend-service.md @@ -0,0 +1,14 @@ +# 18 — Contract the broad Borrow backend service + +**What to build:** Remove the broad Borrow service after all Borrow reads and operations use their separate capability contracts while retaining one private generated-client-backed implementation. + +**Blocked by:** 14 — Share Borrow catalogs and positions; 15 — Route Borrow workflows through BorrowOperations. + +**Status:** implemented + +- [x] No production caller imports or resolves the broad Borrow backend service. +- [x] Runtime composition provides `BorrowResourceSource` and `BorrowOperations` from one private implementation layer. +- [x] Missing Borrow configuration remains a typed capability failure. +- [x] Generated Borrow client types remain private to transport and adapter infrastructure. +- [x] The broad service contract, constructor, layer, and dead compatibility helpers are removed. +- [x] Borrow resource, flow, workflow, lint, type-check, and hygiene validation pass. diff --git a/.scratch/authoritative-resources/issues/19-enforce-resource-dependency-graph.md b/.scratch/authoritative-resources/issues/19-enforce-resource-dependency-graph.md new file mode 100644 index 000000000..9d50f8f97 --- /dev/null +++ b/.scratch/authoritative-resources/issues/19-enforce-resource-dependency-graph.md @@ -0,0 +1,16 @@ +# 19 — Enforce and verify the final dependency graph + +**What to build:** Make Authoritative Resource ownership mechanically durable by rejecting cache-bypassing imports and verifying that the final module graph contains no broad backend-service escape hatch or duplicate remote-data owner. + +**Blocked by:** 16 — Contract the broad Yield backend service; 17 — Contract the broad Legacy backend service; 18 — Contract the broad Borrow backend service. + +**Status:** implemented + +- [x] Module boundaries represent application composition, features, Resources, runtime, services, and domain/shared foundations in the agreed dependency direction. +- [x] Features cannot import read-source capabilities, generated clients, or transport infrastructure. +- [x] Resources cannot import features, React, operation capabilities, generated clients, or transport infrastructure. +- [x] Operation capabilities are imported only by intent-owning command and workflow modules. +- [x] Generated clients are reachable only from private adapter and transport infrastructure. +- [x] Searches and hygiene checks prove there are no broad backend services, duplicate fetch atoms, module-global remote caches, or ad hoc runtimes. +- [x] Architecture documentation matches the enforced graph and names the permitted exceptions. +- [x] Full widget lint, type checking, test suites, build, and hygiene checks pass. diff --git a/.scratch/authoritative-resources/spec.md b/.scratch/authoritative-resources/spec.md new file mode 100644 index 000000000..263511cdd --- /dev/null +++ b/.scratch/authoritative-resources/spec.md @@ -0,0 +1,265 @@ +# Authoritative Resources for Shared Remote Data + +Status: ready-for-agent + +## Problem Statement + +Remote API reads are currently owned by feature-local atoms and broad backend services. Equivalent requests to the same endpoint are represented by different atoms in Earn, Portfolio, Activity, Borrow, and widget-shell code, so the widget can fetch the same canonical fact more than once, apply different freshness policies to it, decode it into incompatible feature shapes, and invalidate only some of its consumers. The current layout makes the feature that happens to issue a request look like the owner of remote data that is actually shared across the application. + +The duplication is already visible in yield details, yield listings, aggregate yield positions, token-balance scans, and validator pagination. Some duplicates call the same endpoint under different names; others differ only because a feature immediately projects the response into its own view model. This fragments the cache and obscures correctness problems. Selection-specific filtering can remove historical positions from portfolio totals, category availability checks can repeat equivalent bounded requests, and identical data may use different stale times depending on which feature requested it. + +The backend abstraction is also too broad. Yield, Legacy, and Borrow services currently expose large generated-client-shaped surfaces to application code. That permits features to bypass a shared cache owner, makes dependency boundaries difficult to enforce, and couples callers to transport concerns and unrelated endpoint families. Simply moving all API atoms into one global module would centralize filenames without establishing coherent ownership; it would create a shallow registry with broad dependencies and no semantic boundary between cacheable facts and state-changing operations. + +The widget needs one authoritative owner for every cacheable canonical remote fact, shared across all consuming features and scoped to one Widget Instance. That owner must define request identity, decoding, freshness, retry, pagination, concurrency, stale-result suppression, failure normalization, and invalidation. Features should bind current UI state to explicit resource inputs and derive feature views, while commands and multi-step workflows remain with the feature or workflow that owns the user intent. Backend access must be split into narrow read and operation capabilities so dependency rules can prevent feature code from reaching generated clients or transport infrastructure directly. + +## Solution + +Introduce a top-level Resources architectural tier composed of separate named deep modules. Each Authoritative Resource is the sole owner of one cacheable canonical remote fact and exposes a small typed Effect Atom interface. Equivalent requests made anywhere in the widget resolve to the same resource identity and therefore share acquisition, cache state, refresh work, and failures within the current Widget Instance registry. + +Resources accept complete explicit inputs and never read feature state, selected state, current wallet state, or route state. They cache canonical decoded facts rather than transport DTOs or feature-shaped read models. Features bind concepts such as current wallet, selected yield, visible items, and route-specific filters to resource inputs, then project the canonical result for presentation without starting a second fetch. + +The Resources tier owns cacheable reads only. User operations remain command atoms or workflow modules in the feature that owns the intent. Successful operations publish semantic invalidation events so every affected query variant becomes coherent. Direct refresh is reserved for user refresh and retry behavior. Pagination, request chunking, concurrency, stale-while-revalidate behavior, retry policy, and typed failure normalization stay behind each resource interface. + +Replace the broad Yield, Legacy, and Borrow backend service surfaces with coarse capability ports. Yield and Borrow each expose a read source and an operations capability. Legacy currently exposes a read source only because its POST-based scans are semantically queries. One shared backend layer may implement both capabilities for a backend, but each capability has its own importable service contract. Generated clients and the transport service remain private adapter infrastructure. + +Enforce the dependency direction from application composition through features, Resources, application runtime, capability services, and domain/shared foundations. Features may consume Authoritative Resources and operation capabilities, but may not import read-source capabilities, generated clients, or transport infrastructure. Resources may consume read-source capabilities but may not depend on feature state. Migrate in resource-sized vertical slices, deleting temporary adapters and duplicate fetch owners as each slice lands, then remove the broad backend services and enable the final strict dependency rules. + +## User Stories + +1. As a widget user, I want two screens requesting the same yield fact to share one fetch, so navigation and simultaneous rendering do not produce duplicate network work. +2. As a widget user, I want equivalent yield-list requests from different features to observe the same cached result, so Earn and Portfolio do not disagree because they refreshed independently. +3. As a widget user, I want aggregate yield positions requested by Earn and Portfolio to come from one authority, so balances and totals stay coherent. +4. As a widget user, I want token-balance scans requested by different features to share the same canonical result, so my wallet is not scanned repeatedly for the same scope. +5. As a widget user, I want validator lists to load on demand and equivalent screens to share their acquired progress, so opening a validator picker does not eagerly fetch the complete directory or duplicate page requests. +6. As a widget user, I want each available category checked with one shared request using the API maximum page size, so category discovery preserves the established bounded behavior without scanning the complete Yield catalog. +7. As a widget user, I want historical positions included in portfolio totals even when their yields are not currently selected or visible, so presentation filtering cannot corrupt accounting. +8. As a widget user, I want switching between Earn and Portfolio to reuse fresh data, so the widget feels responsive and avoids unnecessary loading states. +9. As a widget user, I want a stale cached value to remain usable according to one resource policy while revalidation occurs, so every consumer has consistent refresh behavior. +10. As a widget user, I want retrying a failed resource to refresh the shared authority, so all consumers recover together. +11. As a widget user, I want a manual refresh to update the shared resource, so I do not need to refresh the same fact separately on every screen. +12. As a widget user, I want an operation that changes positions to invalidate every relevant position query, so no screen continues displaying a cached pre-operation position. +13. As a widget user, I want an operation that changes balances to invalidate every relevant balance query, so dependent screens become coherent after the operation. +14. As a widget user, I want activity-producing operations to invalidate relevant activity resources, so new history appears without unrelated cache resets. +15. As a widget user, I want borrow operations to invalidate affected borrow positions and markets, so Borrow screens do not retain stale command results. +16. As a widget user, I want semantic invalidation to cover all query variants affected by a change, so a differently filtered or paginated view cannot remain stale. +17. As a widget user, I want an empty collection of requested identifiers to complete without network I/O, so empty UI states do not trigger meaningless requests. +18. As a widget user, I want requests containing repeated identifiers to avoid duplicate backend work, so enrichment and bulk loading are efficient. +19. As a widget user, I want a partial or missing identifier result represented explicitly, so missing remote facts are not mistaken for empty or successful data. +20. As a widget user, I want transport failures, decode failures, and violated response invariants distinguished, so the UI can present appropriate retry and error behavior. +21. As a widget user, I want a slower obsolete request unable to overwrite a newer result, so rapid wallet, network, or filter changes cannot publish stale data. +22. As a widget user, I want changing wallet or network identity to produce a different resource request, so cached data from one scope is never shown in another. +23. As a widget user, I want semantically equivalent input ordering to share a request where ordering is irrelevant, so harmless collection order changes do not fragment the cache. +24. As a widget user, I want semantically distinct requests to remain separate, so canonicalization never merges inputs that can produce different results. +25. As a widget user, I want large requests to be chunked or paginated behind the resource boundary, so transport limits do not leak into feature behavior. +26. As a widget user, I want a full-result resource to publish success only after its hidden pagination policy is satisfied, so callers do not accidentally treat a partial page as the complete fact. +27. As a widget user, I want a product-defined load-more experience to expose semantic continuation rather than raw transport page mechanics, so pagination behavior matches the UI intent. +28. As a widget user, I want provider enrichment to deduplicate provider lookups, so a list with many yields from one provider does not repeat the same work. +29. As a widget user, I want provider-enrichment failure behavior to be consistent and explicit, so one auxiliary failure cannot unpredictably change the whole catalog result. +30. As a widget user, I want prices, histories, rewards, KYC facts, and health data to follow the same ownership rules, so resource behavior is predictable beyond yields. +31. As a widget user, I want Activity reads to have one authoritative cache owner, so history summaries and detail surfaces do not independently fetch the same records. +32. As a widget user, I want Borrow catalog and position reads to share canonical resources, so distinct Borrow views do not create parallel caches. +33. As a widget user, I want remounting the widget after unmount to start with a fresh resource registry, so data and in-flight work from an old Widget Instance cannot leak into the new one. +34. As a widget user, I want unmounting the widget to interrupt or release resource work owned by that instance, so obsolete requests cannot continue publishing application state. +35. As a widget host, I want the public React component API to remain compatible, so this internal architecture change does not require integration changes. +36. As a widget host, I want the bundled renderer API to remain compatible, so CDN and imperative integrations continue to work. +37. As a feature developer, I want to request a named domain resource rather than choose an API method, stale time, retry count, or page size, so remote-data policy has one owner. +38. As a feature developer, I want resource inputs to be explicit and complete, so a resource can be understood and tested without hidden access to feature or wallet state. +39. As a feature developer, I want current-wallet and selected-yield binding to remain in the feature layer, so shared resources are reusable and do not import UI context. +40. As a feature developer, I want to derive view-specific models from canonical resource facts without creating another fetch atom, so presentation remains flexible without fragmenting the cache. +41. As a feature developer, I want read-only resource state exposed to React, so view code cannot mutate canonical storage or coordinate asynchronous fetching. +42. As a feature developer, I want command atoms to dispatch user intent and publish semantic invalidation after successful operations, so workflows remain near their domain behavior. +43. As a feature developer, I want simple operations to call an operation capability directly from their owning command atom, so the architecture does not require shallow pass-through workflow modules. +44. As a feature developer, I want complex operations to live in deep intent-owning workflow modules when they coordinate multiple steps, retries, state transitions, or cleanup, so that complexity is hidden behind a narrow command surface. +45. As a feature developer, I want Legacy POST scans treated as cacheable reads when they do not change server state, so HTTP verbs do not determine architectural ownership. +46. As a feature developer, I want Yield, Legacy, and Borrow read access represented by separate named capabilities, so a feature cannot receive an entire backend client merely to read one fact. +47. As a feature developer, I want Yield and Borrow operations represented separately from their read sources, so command access does not also grant cache-bypassing read access. +48. As a maintainer, I want every cacheable canonical remote fact to have exactly one Authoritative Resource, so ownership can be located without tracing feature-local fetches. +49. As a maintainer, I want Resources to be a tier of named modules rather than one global API atom registry, so each module remains deep, cohesive, and independently testable. +50. As a maintainer, I want exact semantic request sharing to be the default, so deduplication is correct without requiring a risky global normalized entity store. +51. As a maintainer, I want cross-request entity normalization enabled only for a resource whose merge semantics and identity invariants are explicitly proven, so partial responses cannot corrupt canonical facts. +52. As a maintainer, I want canonical decoded domain facts cached instead of generated transport DTOs, so transport schema changes have one adaptation seam. +53. As a maintainer, I want feature-shaped projections excluded from canonical storage, so one feature cannot silently redefine the data observed by another. +54. As a maintainer, I want each Authoritative Resource to normalize failures into a stable typed vocabulary, so raw generated-client and transport errors do not escape into features. +55. As a maintainer, I want freshness, polling, retry, concurrency, pagination, and stale-result policy owned by the resource, so callers cannot create divergent behavior for the same fact. +56. As a maintainer, I want cache state to use the existing Widget Instance Atom registry, so no module-global cache or ad hoc runtime undermines lifecycle and invalidation. +57. As a maintainer, I want generated backend clients hidden behind adapters, so application modules do not depend on generated client organization or transport details. +58. As a maintainer, I want the transport service to remain the single private owner of base URLs, API credentials, headers, retries, geo-block handling, and rich transport errors, so the refactor does not duplicate infrastructure. +59. As a maintainer, I want one implementation layer to be able to provide both read and operation capabilities for a backend, so capability separation does not require duplicate client construction. +60. As a maintainer, I want the broad Yield, Legacy, and Borrow API service interfaces removed after migration, so there is no permanent escape hatch around Authoritative Resources. +61. As a maintainer, I want dependency rules to reject feature imports of read sources, generated clients, and transport infrastructure, so cache ownership is mechanically enforced. +62. As a maintainer, I want dependency rules to reject Resource imports of feature state, so resources cannot gain hidden current-selection or current-wallet dependencies. +63. As a maintainer, I want operation capabilities available only to command and workflow owners that need them, so read-only view code cannot mutate remote state. +64. As a maintainer, I want reverse-dependency and hygiene checks to detect forbidden imports in continuous validation, so architectural erosion fails early. +65. As a maintainer, I want migration to proceed one resource-sized vertical slice at a time, so behavior can be proven and duplicate owners deleted incrementally. +66. As a maintainer, I want temporary adapters deleted inside the slice that introduces them, so the codebase never settles into permanent dual fetching or two authorities. +67. As a maintainer, I want each migrated resource to replace its old atoms and callers completely before the next slice depends on it, so cache behavior remains comprehensible during the transition. +68. As a maintainer, I want the clearest duplicated resources migrated first, so the architecture is validated against real shared-cache problems before lower-value endpoints move. +69. As a maintainer, I want the final dependency restriction enabled only after the legacy broad services are removed, so the migration can remain buildable without weakening the end-state rule. +70. As a test author, I want an Authoritative Resource's public contract to be the primary behavioral seam, so tests remain stable if pagination, caching, or adapter internals change. +71. As a test author, I want controllable in-memory read-source capabilities beneath resource tests, so request sharing, freshness, interruption, and failure behavior can be verified deterministically. +72. As a test author, I want backend adapter tests below the resource seam, so generated-client request mapping and transport error conversion are covered without duplicating resource behavior tests. +73. As an AI coding agent, I want named resources, explicit inputs, narrow capabilities, and enforced dependencies, so ownership and allowed change surfaces are discoverable from the module graph. +74. As a reviewer, I want a resource migration to show removal of duplicate fetch paths and broad-service imports, so centralization is demonstrated by deletion rather than only by adding a new abstraction. + +## Implementation Decisions + +### Resource ownership and vocabulary + +- An Authoritative Resource is the sole owner of one cacheable canonical remote fact. The term is architectural vocabulary and does not introduce a new product-domain concept. +- The Resources tier is a collection of separate named deep modules, not a single global registry API, generic endpoint wrapper, or file containing all atoms. +- Resource boundaries follow semantic facts rather than backend endpoint grouping or consuming feature structure. A resource may serve multiple features, and one feature may compose several resources. +- A backend endpoint may support several Authoritative Resources when consumers require different semantic contracts, such as demand-driven Pull, complete collection, bounded summary, or point lookup. Sharing is mandatory within an equivalent semantic contract, not across contracts with different completeness or continuation guarantees. +- Exact semantic request sharing is mandatory. Two calls that represent the same complete request identity use the same Effect Atom resource and share acquisition, cached state, revalidation, and failure state inside one Widget Instance. +- Cross-request entity normalization is opt-in per resource. It requires documented stable identity, field completeness, merge semantics, invalidation behavior, and evidence that combining responses cannot create a fact the backend never returned. +- Resources cache canonical decoded facts. Generated transport DTOs are adapted at the backend boundary, while feature-specific filtering, grouping, totals, selection, visibility, and presentation models remain downstream projections. +- Every resource exposes a named typed interface. Do not introduce a generic resource registry in which callers provide endpoint names, fetch functions, page policy, arbitrary cache keys, or retry options. +- A resource's internal mutable cache storage remains private. Consumers receive read-only state and narrowly defined refresh or retry commands where product behavior requires them. + +### Inputs, keys, and cache identity + +- Resource inputs contain the complete identity of the remote fact, including all wallet, network, protocol, yield, token, locale, filter, sort, and other request dimensions that can alter the result. +- Resources never read current Wallet Scope, route state, feature atoms, selected yield, visible collections, or configuration implicitly. Features snapshot or bind those values into explicit typed resource inputs. +- Key canonicalization is semantic and resource-specific. Irrelevant collection ordering and duplicate identifiers may be normalized; meaningful ordering, multiplicity, absence, default values, and filter distinctions must remain part of identity when they affect the response. +- Empty identifier collections and other semantically empty requests are handled before adapter I/O and return the resource-defined empty fact. +- Cache keys must not depend on unstable object identity, generated-client instances, timestamps, or caller-chosen cache labels. +- Cache state, request sharing, and in-flight fibers are scoped to the existing Atom registry owned by one Widget Instance. No module-global caches, Promise maps, secondary Atom registries, or ad hoc Effect runtimes are introduced. +- Sequential Widget unmount and remount creates fresh resource state. The design does not attempt to share caches between separate Widget Instances or browser documents. + +### Resource policy and failures + +- Each Authoritative Resource owns freshness duration, stale-while-revalidate behavior, polling eligibility and cadence, retry eligibility and backoff, request concurrency, chunking, pagination, stale-result suppression, and disposal behavior. +- Callers cannot override resource policy. A genuinely different product contract is modeled as a distinct semantic resource or a named operation on the same resource, not as caller-provided transport settings. +- Resources hide transport pagination. A remotely paginated product list defaults to demand-driven semantic Pull behavior, while a bounded canonical collection returns the complete applicable result only when a consumer explicitly requires completeness. Bounded summaries and point lookups remain separate contracts rather than scanning a complete collection. +- Demand-driven resources use one memoized Pull Atom per semantic query so equivalent consumers share acquisition and accumulated progress. A feature facade may map, filter, enrich, or otherwise project the Pull result, but it must forward the resource's Pull and Refresh commands instead of creating a second pagination stream. +- Pull resources rely on native Atom and Stream semantics for continuation, accumulation, waiting, failure, disposal, and refresh. Continuation is derived only from the backend response's offset, limit, and total. Refresh reconstructs the stream from its first page, and a later-page failure retains already accumulated values according to native Pull behavior. +- Repeated Pull while a request is waiting is prevented by the UI's published waiting state. Resources do not introduce private page caches, manual offset state, request locks, replay buffers, or custom refresh generations to coordinate pagination. +- Bulk and enrichment resources deduplicate identifiers and share repeated subrequests where semantics permit it. Provider enrichment explicitly defines whether auxiliary failures fail the whole fact, produce typed partial data, or use a documented fallback. +- Resource acquisition maps request, transport, decode, and invariant failures into stable typed resource failures. Raw generated-client exceptions and transport DTO error unions do not cross the resource interface. +- Missing requested entities are modeled explicitly according to the resource contract. They are not silently dropped when dropping them would make a partial response appear complete. +- Refresh generations, interruption, or equivalent Atom resource semantics prevent results from obsolete inputs or disposed scopes from replacing newer state. +- Retry repeats the resource-owned acquisition policy. User refresh and retry may trigger direct refresh; ordinary command completion uses semantic invalidation instead. + +### Commands, operations, and invalidation + +- The Resources tier owns cacheable reads only. It does not own transactions, mutation workflows, signing, submissions, multi-step commands, or user-intent state machines. +- A feature's command atom may call an operation capability when the action is simple and the atom already forms a deep intent boundary. Do not add a pass-through command module that only renames one operation call. +- A dedicated operation or workflow module is introduced when it hides meaningful coordination such as multiple backend calls, wallet interaction, retries, concurrency, state transitions, compensation, or scoped cleanup. +- Successful operations publish semantic invalidation events such as wallet balances changed, yield positions changed, activity changed, borrow positions changed, or borrow markets changed. Events describe changed facts rather than naming cache keys or feature screens. +- Each Authoritative Resource declares which semantic invalidations affect it and refreshes or marks stale every relevant query variant. Operation modules do not enumerate individual cached request keys. +- Semantic invalidation is the normal cross-module coherence mechanism. It can invalidate resources not currently mounted without forcing an immediate fetch; active resource policy determines revalidation timing. +- Direct refresh remains available only where a user explicitly refreshes or retries a resource. Features do not use direct refresh as a substitute for publishing the semantic effects of a command. + +### Backend capabilities and dependency boundaries + +- Replace broad backend services with coarse capability ports: `YieldResourceSource`, `YieldOperations`, `LegacyResourceSource`, `BorrowResourceSource`, and `BorrowOperations`. +- Read-source capabilities contain backend access needed to acquire canonical facts. Operation capabilities contain state-changing calls. They use distinct service contracts and import paths even when one shared implementation layer constructs and provides both. +- Legacy exposes no operations capability until it has a genuine state-changing use case. A POST request used for token scanning remains part of `LegacyResourceSource` because query versus command is determined by semantics, not HTTP method. +- Resource modules may depend on the corresponding read-source capability. Features, view adapters, and workflows do not import read-source capabilities directly. +- Feature command atoms and intent-owning workflow modules may depend on operation capabilities. Read-only resource modules do not depend on operation capabilities. +- Generated Yield, Legacy, and Borrow clients remain private implementation details of backend adapters. They are not imported by Resources, features, React views, or domain modules. +- The transport service remains private infrastructure responsible for client construction, base URLs, credentials, common headers, retry behavior, geo-block recognition, and transport-level error detail. +- Broad `YieldApiService`, `LegacyApiService`, and `BorrowApiService` contracts are removed after their callers migrate. No compatibility facade remains that exposes the full generated-client-shaped surface internally. +- The public widget component and bundled renderer remain compatible. Removing broad backend services is an internal architecture change and does not remove or rename the package's public bundle entry. +- The mechanically enforced dependency direction is application composition to features to Resources to application runtime to services to domain/shared foundations, with narrow exceptions for established foundational dependencies that do not bypass resource ownership. +- Boundary checks prohibit features from importing read sources, generated clients, or transport infrastructure; prohibit Resources from importing features or React; and prohibit application logic from using React-owned fetching. +- Operation-capability access is permitted only from owning command or workflow modules. The rule should be encoded with module-boundary and restricted-import checks rather than relying on filename convention or review memory alone. + +### Initial resource boundaries + +- Yield detail and initial-yield lookups become one authoritative semantic resource when their endpoint and identity are equivalent. Caller-specific initialization behavior remains a feature projection or workflow concern. +- General yield listing, available-yield listing, and token-scope yield listing share the same canonical listing resource where they are endpoint-equivalent. Availability and token-scope views are derived or expressed as explicit semantic inputs rather than separate fetch owners. +- Yield positions become one canonical resource used by Earn and Portfolio. Selection and visibility filtering occur after canonical position accounting so historical positions cannot disappear from totals. +- Token-balance scanning becomes one canonical resource. Earn- and Portfolio-specific schemas become projections from one decoded fact rather than separate requests. +- Validator discovery has separate semantic contracts over the same endpoint: ordinary and search results use one shared demand-driven Pull per query; preferred validators use an explicit complete resource; and address resolution uses a bounded point resource. Search may advance independent name and address branches concurrently, then merge and deduplicate them deterministically. +- Yield Directory and provider data use named resources with explicit enrichment and failure semantics. The complete directory contract is bounded to explicit requested Yield IDs. Category discovery is a separate bounded-summary contract that issues one first-page request per category using the API maximum page size and checks eligible results from that response. +- Yield-backed token discovery uses a shared demand-driven Pull. Legacy token options remain a complete non-paginated resource and are projected as immediately complete when the product surface supports both sources. +- Activity history uses a shared demand-driven Pull that emits semantic batches containing actions and the backend total. Filter counts use bounded summary requests; neither path scans a complete history. Activity enrichment is a projection over the canonical Pull and does not create a second pagination stream. +- Prices, histories, KYC facts, rewards, and health data each receive a named owner as their slices migrate; they are not placed into a miscellaneous shared API module. +- Yield Directory remains an explicit complete resource for a bounded set of requested Yield IDs, and Borrow Markets remains complete because market-position resolution requires the full applicable set. No Yield resource scans the unfiltered complete catalog; category availability uses the bounded per-category summary contract. +- Borrow catalogs and positions otherwise receive named resources with complete identity and semantic invalidation from Borrow operations. + +### Migration and delivery + +- Migrate by complete vertical resource slices rather than converting every backend service in one change. Each slice introduces the Authoritative Resource, capability access, canonical model, tests, caller migrations, invalidation behavior, and deletion of all replaced fetch owners. +- The first slice covers yield positions and token balances because they have clear cross-feature duplicates and exercise explicit Wallet Scope, canonical models, semantic invalidation, and shared cache behavior. +- The second slice covers the Yield Directory, yield listings, categories, and provider enrichment. The third covers validators. The fourth covers Activity. The fifth covers prices, histories, KYC, rewards, and health. The sixth covers Borrow catalogs and positions. +- Within each slice, temporary adapters may delegate to existing infrastructure only while callers are being moved. They are deleted before the slice is considered complete; there is no permanent dual-fetch period or two cache authorities. +- A migrated caller must consume the resource or a zero-logic feature projection over it. It must not retain a fallback path to the broad backend service. +- Existing freshness and retry behavior is inventoried before choosing the canonical resource policy. Where feature policies conflict, the resource adopts one explicit semantic policy and tests make the behavior change visible. +- Existing results are audited for endpoint equivalence before atoms are merged. Similar names alone do not prove equivalent request identity or response semantics. +- The broad backend services are removed only after all of their reads and operations have moved to the relevant capability ports. The final strict import rule is enabled in the same concluding slice so no unrestricted service path remains. +- Architecture documentation and the accepted decision record remain aligned with the implementation as each slice lands. + +## Testing Decisions + +- The primary behavioral test seam is each named Authoritative Resource interface. Tests construct it inside a fresh Atom registry with controllable in-memory read-source capabilities and observe the same read-only state and commands available to feature consumers. +- This seam is preferred over testing internal atom helpers, cache maps, generated-client calls, or feature hooks because it protects the architectural contract while allowing pagination, canonicalization, and adapter internals to change. +- Backend adapter tests form a lower seam. They verify capability-to-generated-client request mapping, authentication and transport integration boundaries, DTO decoding inputs, and transport failure conversion without repeating cache-policy tests. +- Operation-capability and intent-module tests form a separate lower seam for commands. They verify operation request mapping, typed failures, multi-step coordination where present, and publication of semantic invalidations. +- A contract suite proves that two consumers making exactly equivalent requests receive one resource identity and cause one acquisition while the result is fresh. +- Equivalent-request tests cover separate feature projections, simultaneous subscriptions, sequential subscriptions, reordered set-like identifiers, duplicate identifiers, and normalized defaults that are semantically equal. +- Distinct-request tests cover every identity dimension that can alter a response, including wallet owner, network, yield, token set, filter, sort, locale, protocol, and pagination mode where applicable. +- Empty-input tests prove a semantic empty result and zero read-source calls. +- Key-stability tests prove that cache identity does not rely on object reference, caller name, timestamps, or generated-client identity. +- Canonicalization tests prove that only semantically irrelevant ordering and duplicates are normalized. Counterexamples prove meaningful ordering or multiplicity is not collapsed. +- Lifecycle tests prove cache sharing within one Widget Instance registry, interruption on disposal, no publication from an obsolete generation, no module-global retention, and a fresh cache after sequential unmount and remount. +- Freshness tests use controllable time to cover fresh reuse, stale publication, revalidation, expiry, polling eligibility, and resource-specific retry policy without real timers or network calls. +- Concurrency tests prove multiple subscribers share one in-flight acquisition, a newer generation wins over a slower obsolete request, cancellation does not corrupt a replacement request, and configured request concurrency limits are respected. +- Failure tests independently cover request construction, transport, decoding, invariant, missing-entity, and unsupported-input failures. They assert stable resource failure types and prove raw adapter exceptions do not escape. +- Retry tests prove retry eligibility is owned by the resource, non-retryable failures do not loop, a later-page failure preserves accumulated values, and explicit Retry or Refresh restarts the shared Pull from its first page. +- Manual-refresh tests prove user refresh uses the resource interface and cannot create a feature-local second fetch path. +- Complete-resource pagination tests cover zero pages, one page, multiple pages, backend page-size boundaries, ordering, deduplication, and publication only after the full canonical collection is acquired. +- Semantic Pull tests prove initial acquisition requests only the first backend page, each accepted Pull advances by one backend continuation, no page is fetched eagerly, equivalent consumers share one Pull identity and accumulated progress, waiting disables repeated Pull, and the interface does not leak raw generated-client pagination structures. +- Pagination continuation tests prove the next request is derived from the backend response's offset, limit, and total rather than the requested limit or decoded item count. +- Chunking tests cover empty chunks, exact transport-limit boundaries, multiple chunks, duplicate identifiers across chunks, stable result assembly, bounded concurrency, partial failure, missing IDs, and deterministic canonical ordering. +- Provider-enrichment tests cover no providers, one provider referenced repeatedly, many providers, deduplicated lookup, auxiliary failure policy, missing provider records, and preservation of the base canonical result according to the declared contract. +- Yield-detail tests prove endpoint-equivalent initial and ordinary lookups share one request while feature-specific initialization decisions remain outside the resource. +- Yield-list tests prove endpoint-equivalent catalog views share acquisition and that selection, availability, and token-scope projections do not create new fetches. +- Category tests prove discovery issues exactly one request per category with `offset: 0` and the API maximum page size, applies the category's Yield types, and derives visibility from eligible results in that bounded response. +- Position tests prove Earn and Portfolio share aggregate position acquisition, all historical positions remain available to accounting projections, and visible-yield filtering cannot alter canonical totals. +- Token-balance tests prove Earn and Portfolio share scanning for the same Wallet Scope and token request, while their differing view schemas are pure projections. +- Validator tests prove ordinary and search queries acquire one page per Pull and share progress across equivalent consumers, preferred validators explicitly acquire the complete result, address lookup remains bounded, and search merges its independently paginated name and address branches deterministically. +- Activity tests prove history acquires one backend page per Pull, exposes action batches with the backend total, uses bounded total requests for filter counts, never completes history merely to paginate in memory, and receives command invalidation without eagerly fetching inactive variants. +- Token-discovery tests prove the Yield source acquires one page per Pull while the Legacy source publishes its complete non-paginated list immediately and exposes no fake continuation. +- Borrow tests prove catalogs, markets, and positions share within their exact identities and respond to the appropriate Borrow operation invalidations. +- Semantic-invalidation tests publish each supported event and assert that every affected resource variant becomes stale or revalidates according to its policy, while unrelated resources remain fresh. +- Multi-variant invalidation tests include different filters, Wallet Scopes, networks, pages, and mounted/unmounted states so invalidation cannot accidentally target only the command caller's current view. +- Operation tests prove successful commands publish invalidation only after the remote operation succeeds, failed commands do not claim nonexistent changes, and repeated invalidation is safe. +- Feature integration tests mount representative Earn, Portfolio, Activity, and Borrow adapters against one registry and prove their projections share resource acquisition without importing read-source capabilities. +- React adapter tests prove views only render resource state and synchronously dispatch commands; they do not own fetching, retry loops, duplicated loading state, or raw error normalization. +- Dependency tests and hygiene checks prove features cannot import read sources, generated clients, or transport infrastructure; Resources cannot import features or React; and generated clients are reachable only from private adapters. +- Migration-slice tests include a search or dependency assertion that no replaced broad-service call or old fetch atom remains for the migrated fact. +- Selective-normalization tests are required only for resources that opt into cross-request entity normalization. They prove entity identity, completeness, merge order independence, invalidation, deletion, and partial-response behavior; absence of these proofs means the resource remains exact-request cached. +- Regression coverage preserves public React and bundled entry behavior, one concurrently mounted Widget Instance, sequential remount, existing routes, and existing feature presentation unless a slice explicitly changes a previously inconsistent cache policy. +- The validation ladder for each slice includes focused resource contract tests, adapter or operation tests when touched, affected feature integration tests, widget lint and type checking, and dependency-hygiene enforcement. Representative browser tests are added where cache sharing, navigation, polling, or lifecycle behavior cannot be proven at a lower seam. + +## Out of Scope + +- Building one global API atom module, generic endpoint registry, generic query builder, or caller-configurable cache framework. +- Introducing a normalized entity store across all endpoints without resource-specific identity and merge proofs. +- Sharing cache state across concurrently mounted Widget Instances, browser documents, users, or sequential application-runtime generations. +- Adding support for multiple concurrently mounted Widget Instances. +- Replacing the existing Atom registry or application Effect runtime with a new caching runtime. +- Introducing page-level cache atoms, manual offset or accumulation atoms, pagination locks, replay buffers, or custom refresh state machines for semantic Pull resources. +- Introducing React Query, hook-owned fetches, Promise caches, module-global maps, or React effects for resource acquisition. +- Moving transactions, signing, submissions, user-intent workflows, or command state machines into the Resources tier. +- Creating shallow operation modules that only forward one call without hiding coordination or policy. +- Changing backend endpoint contracts, generated SDK behavior, authentication, base URL selection, geo-block policy, or transport retry infrastructure except where needed to expose the agreed capability ports. +- Changing public package exports, the React component contract, the bundled renderer contract, routes, UI design, translations, or product copy. +- Guaranteeing that similarly named endpoints are equivalent without auditing their complete request and response semantics. +- Preserving conflicting feature-specific stale times merely for internal behavioral compatibility; each resource must choose and document one canonical policy. +- Performing the migration as an all-at-once rewrite or retaining permanent compatibility facades around the broad backend services. +- Adding Authoritative Resource to the product domain glossary; it remains implementation architecture terminology. + +## Further Notes + +- The centralization goal is ownership, not physical colocation. A discoverable Resources tier provides one place in the architecture to look, while separate named modules preserve cohesion and information hiding. +- “Route commands through deep intent-owning operation modules” means a user intent with meaningful orchestration is represented by a module that owns that orchestration and consumes an operation capability. It does not mean every generated-client method receives a same-shaped command wrapper. +- `YieldResourceSource` and `YieldOperations`, and their Legacy and Borrow counterparts, are Effect service contracts. They are capability seams below Resources and commands, not resource caches themselves. +- The transport service and generated clients still exist as private implementation machinery. What disappears is the ability for arbitrary application code to receive the full backend client surface. +- POST-based reads demonstrate why semantic ownership matters more than HTTP conventions: a scan that only retrieves facts belongs to a read source and may be cached by an Authoritative Resource. +- Exact-request sharing is the safe baseline. It delivers the primary benefit—one cache and one in-flight acquisition for equivalent calls—without creating the correctness risks of merging partial entities returned by different endpoints. +- Pagination mode is part of the semantic contract. Two consumers share acquisition and progress when they use the same Pull contract; a complete, summary, or point contract over the same endpoint remains distinct because it promises a different fact. +- Historical behavior is intentionally preserved where it represented product semantics: Activity history, Yield token discovery, and ordinary/search validators remain demand-driven; preferred validators and Borrow Markets remain complete; Yield Directory is complete only for explicit requested IDs; category discovery remains one bounded maximum-size request per category; and Legacy token options remain a complete non-paginated list. +- The staged order is intentionally front-loaded with duplicated Yield and Legacy reads. Those slices validate the architecture's hardest requirements: cross-feature sharing, explicit Wallet Scope, feature-independent canonical models, pagination, and semantic invalidation. +- A slice is complete only when the new resource is authoritative in practice: all intended callers use it, duplicate fetch owners are deleted, invalidation is wired, its capability boundary is tested, and forbidden direct imports cannot reappear. diff --git a/.scratch/classic-transaction-flow/issues/01-enforce-one-widget-instance-lifecycle.md b/.scratch/classic-transaction-flow/issues/01-enforce-one-widget-instance-lifecycle.md new file mode 100644 index 000000000..c893b95b4 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/01-enforce-one-widget-instance-lifecycle.md @@ -0,0 +1,17 @@ +# 01 — Enforce one Widget Instance lifecycle + +**What to build:** Enforce the supported host behavior of at most one mounted Widget Instance per browser document. A second mount must fail deterministically without disturbing the active widget, while bundled hosts can explicitly unmount and later create a fresh instance. + +**Blocked by:** None — can start immediately. + +**Status:** ready-for-agent + +- [ ] The public embedding boundary acquires one document-level claim before wallet and application providers initialize. +- [ ] The claim uses a stable document-owned identity that detects conflicts across separately bundled widget copies or versions. +- [ ] A second concurrent mount fails immediately with a deterministic internal error name and message, without warning-only behavior, takeover, degradation, or changes to the active instance. +- [ ] The second-mount error is not added to the published package error surface. +- [ ] The bundled renderer returns additive `rerender` and `unmount` operations; rerender retains the claim and unmount releases it with the widget runtime. +- [ ] Sequential unmount and remount creates a clean Widget Instance and fresh runtime generation. +- [ ] Claim ownership prefers a scoped Effect exposed through Atom lifecycle. Any required React mount bridge is isolated as the named embedding boundary and contains no wallet or feature logic. +- [ ] Contract tests cover first mount, rejected second mount, preservation of the first instance, cross-bundle conflict, rerender, unmount, claim release, and sequential remount. +- [ ] Existing published React and bundled entry contracts remain compatible apart from additive bundled unmounting. diff --git a/.scratch/classic-transaction-flow/issues/02-remove-multi-instance-wallet-concurrency.md b/.scratch/classic-transaction-flow/issues/02-remove-multi-instance-wallet-concurrency.md new file mode 100644 index 000000000..41ef6c29f --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/02-remove-multi-instance-wallet-concurrency.md @@ -0,0 +1,15 @@ +# 02 — Remove unsupported multi-instance wallet concurrency + +**What to build:** Simplify wallet runtime code around the supported single Widget Instance contract. Remove synchronization and isolation behavior that exists only for simultaneous widgets while preserving every concurrency mechanism needed for concurrent work inside one widget and for clean sequential remounts. + +**Blocked by:** 01 — Enforce one Widget Instance lifecycle. + +**Status:** ready-for-agent + +- [ ] Audit wallet synchronization and isolation mechanisms by production purpose before removing them. +- [ ] Remove redundant reconnect-initialization serialization and its multiple-initializer contract when Wallet Bootstrap can invoke initialization only once per runtime generation. +- [ ] Remove or rewrite tests that claim simultaneous widget registries or runtimes are a supported production behavior. +- [ ] Retain connector-membership serialization, serialized event and command handling, queues, references, streams, resource deduplication, scoped fibers, and interruption required within one Widget Instance. +- [ ] Retain fresh runtime scopes and disposal behavior required for sequential unmount and remount. +- [ ] Wallet Bootstrap, reconnect, connector discovery, commands, and cleanup retain their existing supported behavior for one widget. +- [ ] Focused wallet tests distinguish removed multi-instance assumptions from retained intra-instance races and lifecycle guarantees. diff --git a/.scratch/classic-transaction-flow/issues/03-enforce-effect-atom-application-boundary.md b/.scratch/classic-transaction-flow/issues/03-enforce-effect-atom-application-boundary.md new file mode 100644 index 000000000..25c3fdf03 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/03-enforce-effect-atom-application-boundary.md @@ -0,0 +1,16 @@ +# 03 — Establish the Effect and Atom application boundary + +**What to build:** Establish React-as-view-layer as the documented design boundary for the Classic Flow effort and future materially refactored code. Application logic must live in deterministic functions, Effect, and Effect Atom, with React limited to rendering, synchronous intent dispatch, presentation-only local state, and explicitly named external boundaries. + +**Blocked by:** 02 — Remove unsupported multi-instance wallet concurrency. + +**Status:** ready-for-agent + +- [ ] Application-logic modules, including the future Classic Flow core and facade, do not import React. +- [ ] Touched view adapters do not use `useEffect` unless the use is isolated in an explicitly named and reviewed external boundary. +- [ ] The document-claim embedding bridge remains only if scoped Effect and Atom lifecycle cannot safely express the React mount contract. +- [ ] The rules permit local synchronous presentation state only when it has no domain meaning, asynchronous behavior, persistence, route lifetime, or cross-component coordination. +- [ ] React event handlers are constrained to normalizing UI input and synchronously dispatching Atom commands; Effect execution and Promise awaiting are not permitted there. +- [ ] Feature facades may expose read-only view Atoms, writable command Atoms, and zero-logic React convenience hooks while keeping mutable storage private. +- [ ] Existing unrelated React effects and React Query usage remain outside this effort's touched scope. +- [ ] The accepted architecture decision and agent guidance remain aligned with the implementation. diff --git a/.scratch/classic-transaction-flow/issues/04-establish-pure-classic-flow-core.md b/.scratch/classic-transaction-flow/issues/04-establish-pure-classic-flow-core.md new file mode 100644 index 000000000..40ce351f6 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/04-establish-pure-classic-flow-core.md @@ -0,0 +1,18 @@ +# 04 — Establish the pure Classic Flow core + +**What to build:** Create a deterministic Classic Transaction Flow model that makes intake identity, variants, phases, replacement, abandonment, and projections explicit without depending on React, Atom storage, browser globals, clocks, randomness, or asynchronous execution. + +**Blocked by:** 03 — Enforce the Effect and Atom application boundary. + +**Status:** ready-for-agent + +- [ ] The model is one tagged union with Enter, Exit, Manage, and Activity Resume variants rather than nullable variant fields. +- [ ] A branded Classic Transaction Flow Identity is injected when a flow starts; the core does not generate identity or read time. +- [ ] Starting Enter, Exit, or Manage creates a Reviewing flow with immutable intake facts; Activity Resume creates an Executable flow with its existing Yield Action. +- [ ] Starting a flow atomically replaces the previous active flow. +- [ ] A targeted Reviewing flow can attach one Yield Action exactly once and transition one-way to Executable. +- [ ] Attachment enforces active identity, phase, and exactly-once invariants without adding cross-field Yield Action content validation. +- [ ] Stale transition attempts return a typed stale-flow result; stale targeted abandonment is an idempotent no-op; other invariant failures leave state unchanged. +- [ ] Pure projections cover Action-preview input, review-pricing input, gas-warning input, Wallet Scope validity, narrow variant snapshots, and Executable Transaction Workflow handoff. +- [ ] Wallet Scope owner comparison remains network plus primary address, with case-insensitive EVM addresses and no invalidation for additional-address-only changes. +- [ ] Table-driven tests cover every variant, transition, replacement, stale case, immutable fact, Back identity rule, and deterministic projection without mounting React. diff --git a/.scratch/classic-transaction-flow/issues/05-build-effect-atom-classic-flow-facade.md b/.scratch/classic-transaction-flow/issues/05-build-effect-atom-classic-flow-facade.md new file mode 100644 index 000000000..8227444ca --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/05-build-effect-atom-classic-flow-facade.md @@ -0,0 +1,18 @@ +# 05 — Build the Effect and Atom Classic Flow facade + +**What to build:** Wrap the pure Classic Flow core in one Effect/Atom application interface that owns reactive state, commands, Action preparation, typed asynchronous state, concurrency, lifecycle resources, and declarative navigation outcomes while keeping mutable storage and Yield Action attachment private. + +**Blocked by:** 04 — Establish the pure Classic Flow core. + +**Status:** ready-for-agent + +- [ ] The facade exposes read-only active and narrow view Atoms plus writable start, Continue, Retry, and targeted-abandon command Atoms; mutable storage is private. +- [ ] Effects run through the appropriate existing scoped application or wallet Atom runtime with injected services; the feature creates no ad hoc runtime and calls neither `Effect.runPromise` nor React-owned asynchronous APIs. +- [ ] Action preview, pricing, gas checks, retries, and invalidation use Effect resources exposed through Atom rather than React Query, hook-owned fetching, or Promise caches. +- [ ] Continue owns preparation loading, typed failures, retry eligibility, exactly-once attachment, and a declarative navigation outcome. +- [ ] Repeated Continue intents for one flow coalesce into one in-flight Action preparation; Retry is enabled after failure rather than creating parallel work. +- [ ] Replacement or abandonment interrupts preparation when possible, and identity checks silently discard any stale completion that cannot be interrupted. +- [ ] Action-preview resource identity includes Classic Transaction Flow Identity; price and gas resources may preserve reusable domain keys across flows. +- [ ] Activity Resume can produce the same Continue navigation outcome without previewing or changing its already-Executable phase. +- [ ] Lifecycle Atoms own acquisition, interruption, abandonment, and finalization for route-scoped work; widget-runtime resources remain scoped to their runtime generation. +- [ ] Facade tests drive Atom commands and observations directly, covering loading, failure, retry, coalescing, interruption, stale suppression, cache identity, navigation outcomes, and scope closure without React effect flushing. diff --git a/.scratch/classic-transaction-flow/issues/06-migrate-enter-journey.md b/.scratch/classic-transaction-flow/issues/06-migrate-enter-journey.md new file mode 100644 index 000000000..700041385 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/06-migrate-enter-journey.md @@ -0,0 +1,19 @@ +# 06 — Migrate the Enter journey + +**What to build:** Move the complete Enter user journey onto the Classic Flow facade, from starting immutable intake through review, Continue, Back, execution, and completion. React must remain a view adapter and the slice must preserve current classic and dashboard behavior. + +**Blocked by:** 05 — Build the Effect and Atom Classic Flow facade. + +**Status:** ready-for-agent + +- [ ] Enter entry points start one Reviewing Classic Transaction Flow through the tagged start command. +- [ ] Enter review consumes a narrow read-only view Atom and normalized pricing and gas-warning projections rather than the legacy request authority. +- [ ] Continue handlers synchronously dispatch the Atom command; React does not await work, run Effects, infer loading locally, or use `useEffect` to navigate. +- [ ] The route adapter renders the declarative navigation outcome only when preparation succeeds for the still-active flow. +- [ ] Returning from Executable to review abandons the old Enter flow and starts a new Reviewing identity with the same immutable intake facts. +- [ ] Continuing the Back-created flow performs a fresh Action preview and attaches a fresh Yield Action. +- [ ] Wallet Scope disconnect or owner change follows existing fallback behavior; casing-only EVM and additional-address-only changes preserve the flow. +- [ ] Tracking and KYC work is initiated by Effect-backed commands at the existing intent or transition boundary with compatible timing and payloads. +- [ ] React code introduced or materially changed by this slice contains only rendering, synchronous intent dispatch, presentation-only local state, and named boundary integration. +- [ ] Unit, DOM, and representative browser tests cover Enter review, loading/failure/retry, Continue, Back, fresh action preparation, steps, completion, routing, and preserved copy. +- [ ] This migration remains on the shared integration branch until ticket 10 completes the atomic cutover. diff --git a/.scratch/classic-transaction-flow/issues/07-migrate-exit-manage-journeys.md b/.scratch/classic-transaction-flow/issues/07-migrate-exit-manage-journeys.md new file mode 100644 index 000000000..da55f65d0 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/07-migrate-exit-manage-journeys.md @@ -0,0 +1,18 @@ +# 07 — Migrate the Exit and Manage journeys + +**What to build:** Move complete Exit and pending-action Manage journeys onto the Classic Flow facade while preserving their distinct review inputs, warnings, routes, tracking, execution, and completion behavior. + +**Blocked by:** 05 — Build the Effect and Atom Classic Flow facade. + +**Status:** ready-for-agent + +- [ ] Exit and Manage entry points start tagged Reviewing flows with immutable variant-specific intake facts. +- [ ] Variant-specific views consume narrow read-only Atoms while shared preview, pricing, gas-warning, Wallet Scope, and lifecycle behavior use normalized facade projections. +- [ ] Continue, loading, typed failure, Retry, and navigation use writable command and view Atoms without React-owned asynchronous orchestration. +- [ ] Returning from Executable Exit or Manage to review creates a new Reviewing identity with the same intake facts and forces a fresh Action preview. +- [ ] The refactor does not inspect, block, compensate for, or model irreversible wallet or submission side effects that may have preceded Back. +- [ ] Tracking and KYC calls remain Effect-backed and preserve existing timing, ordering, payloads, and user-visible state. +- [ ] Existing classic and dashboard routes, Wallet Scope redirects, warnings, steps, completion behavior, and copy remain compatible. +- [ ] Touched React code contains no application logic or unreviewed `useEffect` boundaries. +- [ ] Unit, DOM, and representative browser tests cover both variants through review, Continue, failure/retry, Back, fresh preparation, execution, and completion. +- [ ] This migration remains on the shared integration branch until ticket 10 completes the atomic cutover. diff --git a/.scratch/classic-transaction-flow/issues/08-migrate-activity-resume-lifetime.md b/.scratch/classic-transaction-flow/issues/08-migrate-activity-resume-lifetime.md new file mode 100644 index 000000000..7c264c85d --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/08-migrate-activity-resume-lifetime.md @@ -0,0 +1,18 @@ +# 08 — Migrate Activity Resume and unified flow lifetime + +**What to build:** Replace Activity selection as a parallel transaction authority with the Activity Resume Classic Flow variant, and make every variant obey one route, Wallet Scope, runtime-generation, replacement, and unmount lifetime. + +**Blocked by:** 05 — Build the Effect and Atom Classic Flow facade. + +**Status:** ready-for-agent + +- [ ] Selecting an existing activity Yield Action starts an Executable Activity Resume flow with a new injected Classic Transaction Flow Identity. +- [ ] Activity Resume never requests a new Action preview for its existing Yield Action. +- [ ] Back retains the same Activity Resume identity and Yield Action; a later Continue produces declarative steps navigation for that same flow. +- [ ] Starting any Classic Flow atomically replaces the previous active variant, including replacement between Activity Resume and Enter, Exit, or Manage. +- [ ] The active flow survives ordinary props updates, live rerenders, and bundled rerender. +- [ ] Route exit, Wallet Scope disconnect or owner change, application-runtime generation replacement, and widget unmount abandon the targeted active flow through Atom/Effect lifecycle ownership. +- [ ] Stale lifecycle finalization cannot clear a newer flow, and additional-address-only changes do not invalidate Wallet Scope ownership. +- [ ] React route adapters mount lifecycle Atoms and render view state; they do not coordinate cleanup or workflow transitions through `useEffect`. +- [ ] Unit, DOM, and browser tests cover activity selection, review, Back/Continue identity retention, replacement by other variants, every lifecycle boundary, and existing activity behavior. +- [ ] This migration remains on the shared integration branch until ticket 10 completes the atomic cutover. diff --git a/.scratch/classic-transaction-flow/issues/09-isolate-transaction-workflow-handoff.md b/.scratch/classic-transaction-flow/issues/09-isolate-transaction-workflow-handoff.md new file mode 100644 index 000000000..f4c47d058 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/09-isolate-transaction-workflow-handoff.md @@ -0,0 +1,18 @@ +# 09 — Isolate the Transaction Workflow handoff + +**What to build:** Make Transaction Workflow consume one pure handoff projected from an Executable Classic Transaction Flow, with execution-machine lifetime separated by Classic Flow identity while preserving Yield Action identity and all existing execution semantics. + +**Blocked by:** 06 — Migrate the Enter journey; 07 — Migrate the Exit and Manage journeys; 08 — Migrate Activity Resume and unified flow lifetime. + +**Status:** ready-for-agent + +- [ ] Transaction Workflow handoff is projected from the active Executable flow and is not stored as a second authority. +- [ ] Classic Transaction Flow Identity participates in the local execution-machine generation key so distinct flows cannot share machine state through structural equality. +- [ ] Yield Action ID remains the workflow diagnostic, error, history, and API identity. +- [ ] Activity Resume Back and Continue reuse the same machine generation because the Classic Flow identity is retained. +- [ ] Enter, Exit, or Manage Back creates a new machine generation after the new flow prepares its fresh Yield Action. +- [ ] Submission, signing, confirmation, pending, retry, failure, and completion remain Transaction Workflow state and do not become Classic Flow phases. +- [ ] The Classic flow remains Executable through steps and completion and is abandoned only at its established lifecycle boundary. +- [ ] Execution state and commands remain Effect/Atom-owned; React consumes view Atoms and synchronously dispatches intents without workflow orchestration. +- [ ] Integration tests cover generation separation, Activity Resume reuse, preserved Yield Action diagnostics, completion/history effects, and unchanged execution behavior. +- [ ] This migration remains on the shared integration branch until ticket 10 completes the atomic cutover. diff --git a/.scratch/classic-transaction-flow/issues/10-contract-legacy-intake-and-verify-cutover.md b/.scratch/classic-transaction-flow/issues/10-contract-legacy-intake-and-verify-cutover.md new file mode 100644 index 000000000..cc9b10743 --- /dev/null +++ b/.scratch/classic-transaction-flow/issues/10-contract-legacy-intake-and-verify-cutover.md @@ -0,0 +1,19 @@ +# 10 — Contract legacy intake and verify the atomic cutover + +**What to build:** Complete the atomic Classic Flow cutover by deleting every superseded transaction-intake authority and proving that supported embedding, routing, wallet, review, execution, Effect/Atom ownership, and public API behavior remain coherent as one merge-ready change. + +**Blocked by:** 06 — Migrate the Enter journey; 07 — Migrate the Exit and Manage journeys; 08 — Migrate Activity Resume and unified flow lifetime; 09 — Isolate the Transaction Workflow handoff. + +**Status:** ready-for-agent + +- [ ] Delete the separate Enter, Exit, Manage, and Activity selection state authorities, their setters, action-presence phase encoding, and variant lifecycle atoms and guards. +- [ ] Delete duplicate cross-cutting variant switches for Action preview, review pricing, gas warnings, route validity, and Transaction Workflow handoff. +- [ ] Delete hook-owned asynchronous orchestration, React Query or Promise caching introduced by the legacy Classic paths, and React effects that no longer qualify as named external boundaries. +- [ ] Delete shallow tests for removed atoms, setters, object-identity cleanup, and simultaneous Widget Instance isolation; retain or replace their supported behavior at facade, adapter, browser, and embedding seams. +- [ ] Confirm there is one active Classic Transaction Flow authority with no mirrored writes, compatibility adapter, legacy fallback, or externally writable storage. +- [ ] The final implementation keeps application logic React-free, view handlers synchronous, storage private, resources Effect-backed, cleanup scoped, and external boundaries explicit and reviewed. +- [ ] Representative Enter, Exit, Manage, and Activity Resume browser journeys pass in classic and dashboard routing, including deep links, review, Continue, Back, steps, completion, Wallet Scope redirects, tracking, KYC, warnings, and preserved copy. +- [ ] Embedding tests pass for second-mount rejection, first-instance preservation, bundled rerender/unmount, and sequential remount. +- [ ] Published React and bundled interfaces remain compatible apart from the additive bundled `unmount` operation. +- [ ] Focused unit, DOM, and browser suites pass, followed by widget lint/type checking and relevant hygiene checks. +- [ ] The integration branch is green and ready to land as one atomic Classic Flow migration. diff --git a/.scratch/classic-transaction-flow/spec.md b/.scratch/classic-transaction-flow/spec.md new file mode 100644 index 000000000..4358d89c2 --- /dev/null +++ b/.scratch/classic-transaction-flow/spec.md @@ -0,0 +1,162 @@ +# Scope Classic Transaction Flow by Flow Session + +Status: implemented + +## Problem Statement + +The recent Classic Transaction Flow refactor established one deep module and removed the previous Enter, Exit, Manage, and Activity Resume request authorities. It also introduced a global `keepAlive` facade whose active flow, preparation state, navigation outcome, lifecycle cleanup, and Transaction Workflow handoff are coordinated through a branded Classic Transaction Flow Identity. + +That identity now does too many jobs. It names the journey, separates Action-preview resources and execution machines, targets commands and cleanup, suppresses stale asynchronous completions, and validates navigation. The model also stores `Reviewing | Executable` even though the router already owns Review, Steps, and Complete and action presence already determines whether execution is available. Back must replace an otherwise unchanged flow solely to invalidate its action and resource generation. + +The four intake variants remain meaningful because they capture different required facts and action sources. The over-modeling is in their shared lifecycle, not in the tagged intake itself. + +The widget needs a smaller authority shaped around the actual React and Atom lifetime: a runtime-level intake handoff enters one Flow Session tree, the tree owns its Atoms and resources, and leaving the complete journey disposes them. Equal user attempts are separated by a private runtime-local session epoch. That epoch exists only for React remounting, guarded external cleanup, and stale shared-router suppression; it is not a domain identity threaded through state and commands. + +The accepted contract of at most one concurrently mounted Widget Instance per browser document remains unchanged. Concurrency and stale-work protection required inside that Widget Instance also remain required. + +## Solution + +Keep a small Widget application-runtime intake store containing the current immutable Flow Session entry. Every explicit Start captures the tagged intake, advances a monotonic runtime-local epoch, and creates a fresh `{ epoch, intake }` entry, even when its intake is structurally equal to a previous attempt. The epoch is used only by the session root's hidden compare-and-clear finalizer and the React session boundary key so an exiting tree cannot clear or retain a replacement. + +Use the route's keyed `ScopedAtom` provider to create one session root Atom for the mounted Flow Session entry. Its Atoms use the existing application Atom registry, are not `keepAlive`, and are disposed with their mounted tree. A stable Flow Session route spans Review, Steps, and Complete. Session, Review, and Execution each expose one narrow scoped capability instead of one combined nullable context. Descendants dispatch keyless commands against the capability available in their subtree. The root Atom contains the only feature finalizer: epoch-guarded cleanup of the external intake slot. There is no public lifecycle Atom or identity family. + +The intake variants remain `Enter | Exit | Manage | ActivityResume`, but there is no Classic Flow phase state. Review and Execution are fresh subtree modules created with `ScopedAtom` inside the existing application Atom registry. Enter, Exit, and Manage load a fresh Action Preview inside each Review scope. Activity Resume never recycles its selected action into Execution: Enter and Exit history reconstruct fresh preview requests, while Manage history without server passthrough is non-executable. Continue places the reviewed candidate into a private action handoff and publishes a Steps navigation outcome owned by that Review scope. + +The Execution scope reads that action, spans Steps and Complete, and owns its Yield Action and Transaction Workflow. Returning to Review creates a new Review scope, clears the handoff, and permanently discards the previous action and machine. Atom unsubscription interrupts and releases the workflow; no execution key or cleanup Atom is involved. Execution cancellation navigation is owned by the Execution scope, so it cannot replay after unmount. Transaction signing, submission, confirmation, failure, retry, and completion remain Transaction Workflow state. Steps-to-Complete navigation remains a workflow outcome. + +Atom resource interruption, refresh generations, and disposal suppress stale asynchronous publication. The captured Flow Session epoch is compared only when an exiting root clears the runtime intake store or a scoped router output verifies current ownership. Real API, invalid-action, unsupported activity recreation, wallet, and workflow failures remain typed and visible. + +## User Stories + +1. As a widget user, I want Review, Steps, and Complete to belong to one Flow Session so page remounts and animations do not reset my journey. +2. As a widget user, I want every explicit Start to create a fresh session snapshot even when the new intake equals the previous intake. +3. As a widget user, I want my Action Command, selected yield, tokens, validators, and Wallet Scope captured together so later source-page changes cannot alter what I review or execute. +4. As a widget user, I want Review to show action-derived gas and warning information before I Continue. +5. As a widget user, I want Continue to promote only the fresh Action Preview I reviewed into Execution. +6. As a widget user, I want a failed Action Preview to remain retryable without introducing a partially executable flow state. +7. As a widget user, I want returning from Steps to Review to permanently discard the execution action and workflow and require a fresh Action Preview. +8. As a widget user, I want browser Back and host-driven routing into Review to have the same semantics as the widget Back control. +9. As a widget user resuming activity, I want leaving execution to discard its workflow rather than restart or resurrect it through browser history. +10. As a widget user, I want repeated Continue, Back, or Retry input to be harmless rather than produce internal transition errors. +11. As a widget user, I want a late result or navigation from an exiting session unable to affect the current session. +12. As a connected-wallet user, I want disconnect or wallet-owner change to eject and dispose my Flow Session before execution can continue under another owner. +13. As an EVM wallet user, I want address casing differences treated as the same owner. +14. As a wallet user, I want additional-address-only changes to preserve the session and its captured execution inputs. +15. As a widget user, I want normal host-prop updates, live-configuration rerenders, and bundled `rerender` to preserve my Flow Session. +16. As a widget user, I want leaving the whole journey, replacing the application runtime, or unmounting the Widget to release session resources. +17. As a maintainer, I want one tagged immutable intake rather than four mutable request authorities. +18. As a maintainer, I want a scoped facade capability so React descendants cannot target or mutate another session. +19. As a maintainer, I want Action Preview state represented once by its Effect Atom `AsyncResult`, not duplicated by a preparation state machine. +20. As a maintainer, I want Transaction Workflow lifetime owned by a fresh Execution scope rather than session attached-action state or a global flow identity. +21. As a maintainer, I want Review resources and navigation owned by a fresh Review scope while one Execution scope owns Steps and Complete. +22. As a maintainer, I want stale-work protection provided by resource and session lifetimes, with the private session epoch localized to React remounting and shared external boundaries. +23. As a maintainer, I want classic and dashboard routes, deep links, KYC, warnings, tracking, completion behavior, and published copy preserved. +24. As a host developer, I want the existing one-Widget-Instance embedding, bundled unmount, and sequential-remount contracts preserved. + +## Implementation Decisions + +### Intake and Flow Session ownership + +- The runtime intake store is the only state outside the Flow Session tree. Its private state contains the next monotonic epoch and the current immutable `{ epoch, intake }` entry or `null`. +- `start` is synchronous. It snapshots all intake facts, advances the epoch, and atomically replaces the current session. Equal intake still receives a distinct epoch. +- The epoch is local to one Widget application runtime. It uses no timestamp, randomness, object-reference lookup, branded domain key, or UUID. +- The store lives for the Widget application-runtime lifetime so it can bridge the source page and the destination route. The current session is cleared when its matching root exits; the whole store is disposed with runtime replacement or Widget unmount. +- Source UI may synchronously dispatch Start and navigate to its caller-owned Review route. Entry navigation has no asynchronous outcome and is not stored in the session. +- The Flow Session captures a full immutable Wallet Scope and copies mutable collections in the intake snapshot. + +### Scoped facade and React boundary + +- The Flow Session route's `ScopedAtom` creates the private session root Atom directly from the mounted entry. Its Provider is keyed by the entry epoch because Provider input is captured once. It does not use `Atom.family`, whose structural argument equality would conflate structurally equal starts. +- The route tree creates a fresh Review or Execution root Atom with `ScopedAtom.make`. Scoped atoms use the existing application Atom registry; do not create a nested `RegistryProvider` or a second registry. +- Session, Review, and Execution atoms are not `keepAlive` and use immediate idle disposal where necessary. The long-lived intake handoff does not make preview or workflow resources long-lived. +- One stable route boundary spans Review, Steps, and Complete. Navigation between those pages does not end the Flow Session. +- Session, Review, and Execution providers expose three separate narrow scoped capabilities. Do not add a combined context with nullable Review or Execution fields. Descendant hooks are zero-logic adapters, and commands take no session or route-entry key: `continue()`, `back()`, and `retry()`. +- The root finalizer clears the intake store only when the current entry still has its captured epoch. An exiting old root is an idempotent no-op after replacement. +- The facade derives `isCurrentSession` from the runtime intake store. Router outputs and any other shared-world boundary output are suppressed when that value is false, preventing an animated exiting tree from affecting the replacement session. +- Starting a new session or changing routes may temporarily overlap exiting React subtrees. Keyed Session boundaries, scoped child modules, guarded cleanup, and Atom disposal keep those trees isolated; only the newer Flow Session can publish shared navigation. + +### Intake variants and state + +- `Enter | Exit | Manage | ActivityResume` remain tags on the immutable intake because their required facts and action sources differ. +- The tags do not produce four mutable authorities, four lifecycle machines, or four sets of command atoms. Cross-cutting projections branch inside the facade; variant-specific review views remain narrowly typed. +- Remove `ClassicTransactionFlowIdentity`, `ClassicTransactionFlowPhase`, `Reviewing`, and `Executable` from the session model and facade contract. +- The only mutable Session state is the nullable execution-action handoff. Navigation, preview state, and workflow state belong to child scopes, not the Session. +- Execution availability is derived from the session's private action handoff. The router remains authoritative for whether the user is on Review, Steps, or Complete. +- Repeated or out-of-place valid UI intents are idempotent no-ops. Do not replace removed phase and identity variants with a new transition-result protocol. +- Deterministic input copying, projections, validation, and invariant checks remain plain TypeScript. Atom owns reactive state and commands; Effect owns typed asynchronous resources and workflow lifetime. + +### Action Preview and Back + +- Every Review subtree receives a fresh scoped facade. Enter, Exit, and Manage expose one Effect Atom Action Preview resource owned by that scope. The API continues to return the Yield Action candidate needed for gas estimation and warnings. +- Continue is available only after Action Preview success and while KYC permits it. Continue synchronously places that exact candidate into the action handoff and publishes `"Steps"` navigation owned by the Review scope. +- Action Preview loading and failure come directly from its `AsyncResult`. Retry refreshes that resource. There is no separate `Idle | Loading | Failure` preparation union. +- Ordinary preview failure does not place an action in the handoff. Invalid Exit preview content remains a typed, non-retryable failure at the existing validation seam. +- Entering Review from Steps by widget Back, browser history, or host routing creates a new Review scope. Review-scope initialization is the sole operation that synchronously clears the action handoff; the old Execution scope and machine disappear through Atom unsubscription and cannot affect a newer action. +- Back does not create a new Flow Session. The immutable intake snapshot remains unchanged. +- Activity Resume never seeds Execution from its historical action. Enter and Exit history build fresh preview requests. Manage history without the required passthrough is a typed, non-retryable preview failure. +- Review-pricing and gas-warning resources may retain reusable domain cache keys. Review-only subscriptions are released when Review unmounts. +- Execution actions and machines are never resurrected by browser Forward. A later Continue must use a new Action Preview; it does not restart a disposed machine. + +### Navigation and Transaction Workflow + +- Navigation is not session state. Each Review scope owns only `null | "Steps"`; each Execution scope owns only `null | "Review"`. Neither imports or commands React Router. +- Continue publishes Steps from the Review scope. Back publishes Review from the Execution scope. Unmounting the producing scope permanently removes its navigation outcome, so destination-entry consumption is unnecessary. +- The single route adapter renders navigation declaratively and only while the producing scope belongs to the current Flow Session. No session or route-entry identity is carried in the navigation outcome. +- Steps and Complete routes require an action handoff. Review routes are valid from immutable intake and acquire a new Review scope. +- Each Execution scope privately creates its Classic Transaction Workflow from its captured action and mounts the workflow module's dedicated root Atom. That root, rather than the `viewAtom` or a Steps page subscription, retains workflow state and completion subscriptions through both Steps and Complete. It does not pass a session identity through a global workflow-family handoff. +- Unmounting Execution disposes the corresponding machine. A later Continue uses a new previewed action and creates a new machine. +- Yield Action ID remains the workflow diagnostic, API, and history identity. Classic Flow introduces no additional numeric ownership key. +- Signing, submission, confirmation, retry, pending, failure, action-history invalidation, and completion stay inside Transaction Workflow. Steps-to-Complete navigation remains derived from workflow completion. + +### Failure, interruption, and Wallet Scope + +- Atom resource interruption, refresh generation, and node disposal prevent an old preview from publishing into a disposed or refreshed facade. The model does not return `StaleFlow`. +- An external API request may finish remotely after local interruption, but its result cannot attach an action, publish navigation, or mutate the current facade. +- Remove `NotReviewing`, `IdentityNotReplaced`, and other phase/identity coordination results. Preserve typed API, invalid-action, wallet, workflow, and genuine internal invariant failures. +- Wallet Scope owner validity remains network plus primary address, with case-insensitive EVM comparison. Additional-address-only changes neither invalidate the owner nor mutate the captured scope. +- Disconnect or Wallet Scope owner mismatch causes the route boundary to eject and dispose the session. It does not introduce an invalid-wallet facade state. + +### Compatibility and delivery + +- Preserve existing classic and dashboard routes, deep links, Review, Steps, Complete, tracking, KYC, warning behavior, translations, and published copy unless explicitly changed above. +- Preserve the public React and bundled entry APIs, the one-concurrent-Widget-Instance rule, bundled `rerender` and `unmount`, and sequential unmount/remount behavior. +- Perform the scoped-facade conversion atomically. Do not retain the current global facade, phase model, identity service, identity-keyed workflow wrappers, or compatibility mirrors alongside the new authority. +- Keep the Classic Transaction Flow architecture guidance aligned with the implementation: application logic remains React-free, view adapters remain synchronous, and no new React effects own session resources or transitions. + +## Testing Decisions + +- Intake-store tests prove monotonic runtime-local epochs, immutable snapshots, atomic replacement, guarded cleanup, and distinct sessions for structurally equal intake without clocks or randomness. +- Scope and boundary tests prove that equal-intake sessions have isolated session state, every Review and Execution provider gets a fresh scoped facade, and unmount disposes non-`keepAlive` atoms. +- Overlap tests mount an exiting old root and a current replacement together. Old cleanup cannot clear a new store entry, and scoped navigation cannot affect the shared router after its owner is stale. +- Action Preview tests cover eager Review loading, direct `AsyncResult` loading/failure, Retry, promotion of the reviewed candidate, invalid Exit preview, and fresh resources for later Review scopes. +- Back tests cover UI Back, browser history, and host-driven Review entry. Every path acquires a fresh Review scope, discards the execution action and machine, and requires a fresh Action Preview without replacing the intake snapshot. +- Activity Resume tests prove that the historical action is not recycled, reconstructable actions receive fresh previews, and returning to Review disposes the execution machine. +- Command tests prove that repeated Continue, Back, and Retry are idempotent and that facade commands require no key. +- Resource-lifetime tests prove that Review resources release outside Review while one Execution scope and Transaction Workflow remain alive through Steps and Complete. +- Workflow integration tests prove that different Execution scopes cannot share a machine, Activity Resume does not retain a disposed machine, and Yield Action ID remains the diagnostic and history identity. +- Route tests cover missing intake, missing action handoff on Steps, scoped navigation disposal, route-page remounts, animation overlap, and cleanup only after the owning subtree exits. +- Wallet tests cover disconnect, owner change, EVM address casing, additional-address-only changes, and immutable captured execution inputs. +- Runtime tests cover ordinary prop updates, live configuration changes, bundled `rerender`, application-runtime replacement, Widget unmount, and sequential remount. +- Focused behavior and lifecycle tests protect against reintroducing global `keepAlive` flow state, domain flow identity, stored phases, keyed descendant commands, or React-owned asynchronous orchestration. +- Focused DOM route tests cover the production Session, Review, and Execution boundaries, while focused Chromium tests cover real Transaction Workflow execution and interruption. The existing classic and dashboard regression suites continue to protect route, KYC, warning, tracking, completion, and copy behavior outside this lifetime seam. +- Focused unit and DOM suites, representative Chromium suites, widget lint/type checking, and relevant hygiene checks form the validation ladder. + +## Out of Scope + +- Supporting multiple concurrently mounted Widget Instances in one browser document. +- Replacing the existing application Atom registry with nested per-route registries. +- Changing Action-preview API contracts or removing action-derived gas and warning information from Review. +- Validating returned Yield Action address, yield ID, or action type against intake beyond existing focused validation. +- Modeling or compensating for irreversible wallet or submission side effects after Back. +- Moving signing, submission, confirmation, retry, pending, failure, or completion state into Flow Session. +- Redesigning classic or dashboard UI, routes, KYC, warnings, tracking, translations, or copy. +- Changing price or gas-warning domain cache behavior beyond lifecycle wiring. +- Breaking public package entrypoints or the accepted embedding contract. + +## Further Notes + +- This specification uses the repository's Classic Transaction Flow, Flow Session, Action Command, Action Preview, Yield Action, Activity Resume, Wallet Scope, Transaction Workflow, and Widget Instance vocabulary. +- Flow Session epoch is deliberately technical. Equivalent user attempts and overlapping React trees need distinct ownership, but the epoch is not exposed as domain identity or passed through descendant commands. +- The key architectural goal is lifetime alignment: the intake store bridges into the journey, the Flow Session boundary owns only coordination, a fresh Review scope owns review resources, and a fresh Execution scope owns its one-shot action and Transaction Workflow through Steps and Complete. +- Information hiding follows from that ownership. React receives one scoped facade capability and cannot address another session or mutate private storage. +- A synchronization mechanism remains justified when it protects real intra-instance concurrency. Removing Classic Transaction Flow Identity does not remove command serialization, scoped interruption, wallet synchronization, or the private epoch checks at shared external boundaries. diff --git a/.scratch/earn-state-machine/spec.md b/.scratch/earn-state-machine/spec.md new file mode 100644 index 000000000..3a824e678 --- /dev/null +++ b/.scratch/earn-state-machine/spec.md @@ -0,0 +1,317 @@ +# Earn State Machine Contract and Verification + +Status: implemented + +## Problem Statement + +`packages/widget/src/features/earn/state/atoms-state/` is the application-state boundary that combines user intent, widget configuration, Wallet Scope, and several Authoritative Resources into the selection and form consumed by Earn UI. Its behavior is currently covered only in fragments. Important branches are unverified, several loading and failure cases are collapsed into empty data, and some stale or invalid intent can survive behind a fallback projection. + +The module needs an explicit behavioral contract and an exhaustive verification campaign. Tests must prove selection precedence, transition resets, data composition, loading and failure semantics, pagination, retry, refresh, race handling, and capability derivation. When the tests expose behavior that violates this specification, the implementation is corrected in the same effort and the correction is recorded here. + +## Architectural Boundary + +Earn state owns: + +- User intent and deterministic transitions. +- Resolution of Earn Selection from intent and authoritative facts. +- Dependency-aware loading, empty, readiness, and blocking-failure state. +- Reconciliation of invalid selection after authoritative success. +- Stage-targeted first-load retry commands. +- Domain validation represented by `can.*` capabilities. + +Authoritative Resources continue to own canonical remote reads, semantic request identity, caching, pagination, refresh, typed resource failure, semantic invalidation, and stale-result suppression as required by ADR-0008. + +Earn projections consume typed Authoritative Resource results and never inspect HTTP causes directly. Optional preferred-token enrichment treats a failed Legacy token-directory lookup as a missing preference candidate without changing that resource's canonical error contract. Requested initialization lookups remain blocking when their required first acquisition fails. + +The view resolver observes keyed resources lazily in dependency order and normalizes each `AsyncResult` exactly once. Independent initialization and positions reads begin together to avoid a loading waterfall. A usable observation retains its waiting flag so same-key refreshes remain visible without becoming blocking acquisition states. The published view contains normalized token, yield, and positions snapshots; React receives operational atoms only for pagination. Blocking failures carry the already-resolved atom that Retry must refresh. + +`EarnYield` response decoding owns Earn Mechanic Argument projection and validation before authoritative yield facts reach this machine. Every field first decodes through the generated API schema, then the typed API field array resolves to a name-keyed domain record containing only `amount`, `providerId`, `tronResource`, `validatorAddress`, `validatorAddresses`, and `subnetId`. Consumed fields validate the canonical Yield API variants: `amount`, `providerId`, `validatorAddress`, and `validatorAddresses` use type `string`; `tronResource` uses `enum`; and `subnetId` uses `number`. The domain values retain only Widget-consumed required flags, amount bounds, and options rather than wire-only `name`, `type`, or label metadata. Every argument container (`enter`, `exit`, each `manage` action, and `balance`) uses the same projection. The Widget trusts the API contract to provide unique argument names. Valid unrelated fields are projected away; a malformed API field or invalid consumed domain value rejects that yield at the response seam. + +React is a view adapter. It reads the resolved view and dispatches synchronous user commands as required by ADR-0004. It does not reconstruct loading, failure, retry, or domain-validity policy from raw resource flags. + +Both classic and dashboard adapters render blocking `failed` with the shared error treatment and machine Retry command, suppress invalid downstream controls, and disable the CTA. A non-blocking refresh failure retains stale controls and may show a compact warning without replacing the page or activating machine Retry. Successful `no-*` states retain empty treatment and are never rendered as failures. + +## Domain Terms + +The canonical terms are defined in `CONTEXT.md`: + +- **Earn Selection** is the resolved category, token, yield, validators, and form. +- **Earn Initialization** is the one-time seeding of the first selection from init parameters. +- **Earn Readiness** means every fact needed for correct selection and submission eligibility has a usable value. +- **Wallet Scope Owner** is network plus primary address; additional addresses are not owner identity. + +## Machine Invariants + +1. The published selection is valid against the latest successful authoritative facts for the active semantic inputs. +2. A transport or server failure without a usable value is never represented as a successful empty result. +3. Waiting or failed refresh with a prior successful value preserves that value and does not end Earn Readiness. +4. A successful result that removes a selection reconciles and commits a valid fallback; stale hidden intent cannot later snap the selection back. +5. `ready` is published only when every input needed for correct initial selection and submission eligibility has a usable value. +6. A selected yield's consumed mechanic arguments have already decoded into valid domain constraints and options. +7. A yield requiring validator selection has at least one valid selected validator before it is ready. +8. Results from obsolete wallet owners, configuration, categories, tokens, yields, validators, pagination keys, or searches never alter the active view. +9. Earn Initialization runs at most once per Widget Instance. +10. A Wallet Scope Owner change resets intent but does not rerun Earn Initialization. +11. A prior successful value is usable during waiting or refresh failure only when the complete semantic resource key is unchanged. + +## Lifecycle and Reset Rules + +### Wallet resolution + +While wallet resolution is pending, the machine publishes `resolving-wallet`, starts no new owner-scoped loads, disables selection and submission capabilities, and retains the prior view snapshot when one exists. The first pending resolution uses an empty snapshot. + +After resolution settles: + +- The same Wallet Scope Owner continues with existing intent. +- A changed primary address or network resets the complete Earn intent to its default state, then resolves from current authoritative facts without applying Earn Initialization again. +- An additional-address-only change retains intent and refreshes or re-resolves dependent balances and positions. + +### Configuration changes + +- Preference, category-order, and `tokensForEnabledYieldsOnly` changes use new semantic resource keys and reconcile existing intent when possible. +- Validator allow/block/preference configuration changes re-project the canonical validator facts and reconcile any selection that is no longer eligible. +- Switching between classic and category-grouped dashboard modes resets category, token, and every downstream selection and form field. +- Reordering categories does not override an explicit category that remains available. +- Configuration changes never reactivate consumed Earn Initialization. + +### User transitions + +- Re-selecting the same effective category, token, or yield is idempotent. +- Changing category resets token, yield, validators, provider, amount provenance, and Tron resource. +- Changing token resets yield, validators, provider, amount provenance, and Tron resource. +- Changing yield resets validators, provider, amount provenance, and Tron resource. +- Clearing a selection is explicit user intent and cannot reactivate initialization. +- Provider, validator, amount, and Tron commands change only their own field. +- Automatic reconciliation commits through the same reset graph as explicit changes: a reconciled category resets token and downstream state, a reconciled token resets yield and form state, and a reconciled yield resets validators and form state. + +## Dependency and Status Model + +The public status vocabulary is: + +- `resolving-wallet` +- `loading-categories` +- `no-categories` +- `loading-initial-selection` +- `loading-token-options` +- `no-tokens` +- `loading-yields` +- `no-yields` +- `loading-positions` +- `loading-validators` +- `no-validators` +- `ready` +- `failed` + +When several required inputs are waiting, status uses this dependency order: wallet, categories, initialization, token options, yields, positions, validators. Acquisition may run safely in parallel even though the published status has deterministic priority. + +`failed` carries one highest-priority discriminated failure: + +- `ResourceFailure` identifies `categories`, `initial-selection`, `token-options`, `yields`, `positions`, or `validators`, preserves the typed catalog error and raw diagnostic cause, and exposes a Retry command for only the responsible resource. + +The failure is used only when a required first acquisition has no usable value. Retry ignores duplicate commands while its target is waiting. If recovery reveals an independent downstream failure, that failure then becomes active according to dependency priority. A malformed directly requested yield is an ordinary decode-backed resource failure; a tolerant directory omits only the malformed yield and records the decode rejection. + +A later refresh failure retains `ready` and its prior successful value. The responsible resource exposes the non-blocking error; machine-level blocking Retry is not activated. + +Successful empty data uses the matching `no-*` status. It is not a failure and does not offer transport retry. + +## Selection Resolution + +### Category + +- Classic mode always resolves `null`. +- Category-grouped dashboard mode waits for category discovery. +- An explicit or one-time initialized category is accepted only when discovered as available. +- Otherwise, the first discovered category in configured order is selected. +- Successful discovery with no categories resolves `null` and publishes `no-categories`; the machine does not invent a category. +- A category is available when its bounded maximum-size first-page summary defined by ADR-0008 contains at least one enter-enabled yield on a supported network. Earn state does not complete the category catalog to prove existence; if bounded discovery becomes insufficient, the resource contract must move to a dedicated summary endpoint. Reward rate affects ranking, not visibility. + +### Token + +Token precedence is: + +1. Valid explicit user selection. +2. One-time init-yield token. +3. One-time init token, matching exact canonical token key first and then case-insensitive symbol plus network. +4. Preferred token configured for the active network. +5. First merged token option. + +When disconnected, the first configured network may seed a default. When an active wallet network exists without preferences, preferences from another network are not used. + +A configured preferred-token candidate is resolved during initial selection from the complete Legacy token directory response, then intersected with the same API-key and category yield scope before merging. The demand-driven paginated Yield token directory is not eagerly completed to find preferences, and a token discovered only by later browsing does not auto-switch a ready selection. + +Merged token options: + +- Deduplicate by canonical token key. +- Union, deduplicate, and sort available yield IDs. +- Prefer balance values and metadata over init, and init over default. +- Rank positive-balance tokens, init-only tokens, zero-balance tokens, then default-only tokens while preserving source order within each rank. +- Apply category scope to every candidate and remove a token left with no yield IDs. +- When `tokensForEnabledYieldsOnly` is active, intersect yield IDs from every source, including balance and initialization, with the authoritative enabled-yield scope. Initialization cannot bypass API-key product configuration; an excluded init target is consumed as unavailable and falls back. +- On initial connected-wallet acquisition, default discovery, balances, requested init data, and category scope must all settle before the result is authoritative. +- Token provenance is distinct from balance knowledge. Disconnected options have unknown available amount. After a successful connected-wallet scan, a returned amount is authoritative and an otherwise selectable token absent from the scan has known amount `"0"`; waiting or first-load failure leaves the machine unready rather than fabricating zero. + +### Yield + +Yield precedence is: + +1. Valid explicit user selection. +2. One-time valid init yield. +3. Preferred yield for the selected token's network and canonical token key. +4. First yield eligible under balance, positions, and init constraints. +5. First non-zero-reward yield, then first available yield. + +Preferred value `"*"` delegates to eligibility and default ranking. Preferences from another network are not used. + +Yield visibility is determined by API-key-scoped data, enter capability, and supported network. The legacy hard-coded exclusions for Binance BNB and AVAX native staking are removed from both the state resolver and still-live Earn provider/yield helper resources. Zero reward does not hide a yield in state or React selectors; reward affects default ranking only. + +### Validators + +- Authoritative Resources retain raw canonical validators. Earn state applies network or wildcard allow/block/preference configuration and yield-specific eligibility before selection, empty-state, readiness, and capability decisions; React does not filter a second time. +- Preferred-validator acquisition and the first default-validator page both settle before readiness because either can change the correct initial selection. +- Single-select replaces the selection. +- Multi-select toggles membership but cannot remove the final validator. +- Explicit removal is a no-op for the final validator. +- After authoritative refresh, retain the valid selected subset. If none remain, choose a still-valid one-time init validator, then the first preferred validator, then the first active validator. +- Successful required-validator acquisition with no validators publishes `no-validators`. +- Search results have independent keyed pagination, pass through the same eligibility projection, and do not reorder or replace the default loaded list. +- A selected search result may be remembered by the base validator resource. + +### Initialization targets + +- Syntactically invalid init parameters decode to absence before reaching Earn state. +- A well-formed target that resolves as unavailable or disabled is consumed and falls back normally. Transport, server, and decode failures for requested initialization data remain blocking first-load failures. +- A transport or server failure while resolving a requested target is a blocking first-load `initial-selection` failure with retry. +- Account-targeted initialization is not consumed while the wallet owner is absent. Committing a resolved fallback and consuming initialization are separate transitions, so initial wallet connection can reset intent and resolve the same one-time target for its owner without re-arming initialization later. +- Once a yield target is committed, its ID remains a resource seed while selected so transient catalog re-keying cannot replace it with a fallback. +- A consumed, invalidated, or user-overridden target cannot resurrect. +- A successful bounded directory result may explicitly omit requested IDs. Omitted IDs are unavailable selections, not transport failures; explicit intent is reconciled and init intent emits its one diagnostic before fallback. + +## Form Resolution + +### Provider and Tron arguments + +- Explicit values are accepted only if present in the selected yield's advertised options. +- Optional arguments default to `null`. +- Required arguments default to the first advertised option. +- No value such as `ENERGY` is hard-coded outside advertised options. +- `providerId` options decode as `YieldId`; `tronResource` options decode as `TronResource`. +- A required argument without an advertised option, an invalid option, or an unexpected field type rejects the yield during response decoding. +- Refreshed yield metadata reconciles stale option values. + +### Amount + +Amount intent has internal provenance: + +- `untouched` resolves to the yield minimum. +- `manual` preserves the normalized user value, including zero. +- `max` preserves the calculated maximum snapshot for an ordinary yield. +- A force-max yield always derives from the latest available balance and ignores prior manual/max amount. +- Category, token, yield, or Wallet Scope Owner reset returns amount provenance to `untouched`. +- When refreshed yield, position, or balance facts change constraints, `untouched` follows the new minimum, while `manual` and ordinary `max` values remain unchanged and may make `can.submit` false. Enumerated provider and Tron values reconcile to advertised options; user-authored numeric input is not silently rewritten. +- Numeric amount constraints decode as exact finite decimal strings. Missing or null minimum becomes `"0"` and missing maximum becomes `null`; explicit maximum `"0"` retains its unbounded meaning. A maximum of `"-1"` with a non-negative minimum is the Yield API's unbounded-maximum sentinel and normalizes to `null`, while the complete `"-1"`/`"-1"` pair denotes force-max. A negative minimum outside that pair, another negative maximum, or a positive maximum below minimum rejects the yield during response decoding rather than allowing coercion or `NaN`. + +The public form may continue exposing `stakeAmount` and `useMaxAmount`; provenance can remain private. + +## Capabilities and Validation + +Selection capabilities are enabled when their corresponding authoritative options exist and their own dependency is usable. They remain enabled during later pagination or non-blocking refresh. Blocking failures are stage-aware rather than global: category failure removes category-dependent controls; token failure may retain known category choices; yield failure may retain category and token choices; positions or validator failure may retain category, token, and yield choices. Submission and only the controls depending on the failed stage are disabled, so choosing a different upstream option is a valid recovery path alongside Retry. + +`can.submit` requires: + +- `ready` status. +- A connected Wallet Scope Owner. +- Valid token and yield selections. +- Every required validator, provider, and Tron value. +- Non-zero amount satisfying yield minimum and maximum. +- Amount not exceeding known available balance. +- A resolved balance for force-max yields. + +KYC, gas checks, and transaction-execution readiness remain outside this machine. Plain amount and form validation is shared with the UI so the application has one definition. + +## Pagination, Refresh, and Concurrency + +- First-page failure without a usable value is blocking. +- Loading more preserves accumulated items and readiness. +- Later-page failure preserves accumulated items and exposes a non-blocking error. +- Repeating Pull after a later-page failure may retry the same continuation while preserving accumulated items. +- Explicit resource Refresh or Retry restarts the shared Pull from page one, as required by ADR-0008. Machine-level Retry is present only for blocking first-page failure. +- Duplicate items across pages merge by canonical identity. +- Repeated Pull while waiting is ignored. +- Validator search pagination is keyed by normalized search and cannot publish obsolete results into a newer search. +- Explicitly selected search validators may be remembered without changing default-list order. +- Every result is keyed by all semantic inputs. +- Same-key refresh may retain a previous successful value. A changed semantic key clears downstream old-key selection and publishes the new key's loading state; old-key data is never presented as stale data for the new request. +- Late results for obsolete wallet owner, configuration, category, token, yield, validator, or search cannot affect the active view. +- Revisiting a prior semantic key may immediately use its cached successful value under the Authoritative Resource's freshness and invalidation policy. Revisiting alone does not force a fetch. + +## Verification Strategy + +Tests use four seams: + +1. Schema-boundary tests for Earn Mechanic Argument projection, normalization, rejection, tolerant directory omission, and direct-opportunity decode failure. +2. Pure decision tables for reducers, resolvers, validation, precedence, and invariants. +3. Atom-registry integration tests with controllable service Layers and deferred responses for loading, failure, retry, refresh, pagination, reconciliation, and races. +4. A small set of DOM/browser tests proving critical states and commands are consumed correctly by both classic and dashboard UI adapters, including blocking retry recovery and stale-data refresh failure. + +Verification is complete when: + +- Every action and meaningful resolver branch is asserted. +- Every status and capability combination is exercised. +- Initial, waiting, waiting-with-value, success, empty, typed failure, refresh, recovery, pagination, and out-of-order completion are covered. +- Input cross-products are used where inputs genuinely interact. +- Folder-scoped coverage may be inspected to find untested behavior, but no coverage percentage is a completion target or delivery gate. +- Focused unit and DOM tests, affected browser tests, widget lint/type checking, and hygiene checks pass. + +Property-test tooling is not added solely for this effort. Explicit Vitest decision tables and invariants are preferred with the current dependency set. + +## Delivery + +The change lands as one coherent internal cutover. Implementation proceeds test-first in small red/green/refactor steps across pure resolution, catalog projection, machine lifecycle, and UI adapters, but the branch does not retain parallel old/new machine authorities, temporary fallback behavior, or a compatibility facade for behavior this specification replaces. Focused tests run throughout; the completed cutover runs the full widget validation ladder. + +## Expected Behavioral Corrections + +Code inspection identified the following current behaviors that conflict with this specification and must be verified before correction: + +- Token and yield acquisition failures can appear as `no-tokens` or `no-yields`. +- Positions, category, and validator failures can be treated as successful empty facts. +- `ready` can be published before positions or required validators resolve. +- Dashboard token loading can begin from an invented fallback category while discovery is pending. +- The declared `resolving-wallet` state is not reliably published. +- Invalid intent can remain hidden behind a fallback and later snap back. +- Init targets remain permanent fallbacks and can resurrect. +- Wallet Scope Owner changes do not reset complete Earn intent. +- Category changes do not reset token intent. +- Explicit zero amount is indistinguishable from untouched amount. +- Required Tron default is hard-coded instead of derived from advertised options. +- Provider and Tron intent is not reconciled against refreshed options. +- Final-validator removal semantics differ between toggle and explicit remove. +- Three yield IDs are hard-coded out of visibility despite API-key scoping. +- Zero reward can incorrectly remove a dashboard category from availability. +- React's yield selector independently filters zero-reward yields after state resolution. +- Still-live Earn provider/yield helper resources repeat the hard-coded yield exclusions outside `atoms-state`. +- Cross-network preference fallback can influence an active network. +- `can.submit` can be true for disconnected or otherwise invalid form state. + +## Completed Correction Log + +- First-load category, token, yield, position, and validator failures now publish structured blocking failures; refresh failures with a previous value retain readiness. +- Readiness now waits for categories, tokens, yields, positions, and both required-validator sources in dependency order. +- Wallet pending publishes `resolving-wallet` without starting owner-scoped work; address/network owner changes and classic/dashboard mode changes reset intent, while one-time initialization is never re-armed. Account-targeted initialization consumption waits for the initial wallet owner. +- Successful fallback selection is committed to canonical intent, and category changes reset token plus all downstream state. +- Explicit zero amount, connected-wallet balance knowledge, and truthful submission capability are modeled in state. Advertised provider/Tron options and coherent numeric constraints are decoded before state resolution. +- Earn Mechanic Arguments now resolve from API arrays into validated, name-keyed domain records. Resolver-owned `InvalidYieldContract` handling and its form failure stage were removed. +- Preferred token discovery uses the complete Legacy directory and treats optional preference lookup failure as missing; active-network selection never consumes another network's preference. +- Every token source is intersected with category and enabled-yield scope, and stale hard-coded yield exclusions were removed. +- Validator configuration is applied before selection/readiness and React no longer performs a second eligibility filter. +- Zero-reward, enter-enabled yields remain visible in category discovery and selectors. +- Catalog projections forward Refresh to the exact authoritative source, and classic/dashboard failure UI exposes the targeted retry. + +This list becomes a completed correction log as focused tests reproduce each behavior and the implementation is fixed. + +## Out of Scope + +- Replacing or duplicating Authoritative Resource cache and pagination ownership. +- Introducing React Query, hook-owned fetching, Promise caches, or React-owned retries. +- Broad UI redesign, unrelated product copy, KYC policy, gas policy, or transaction execution changes. +- Preserving accidental `atoms-state` behavior when it conflicts with this contract. +- Adding support for multiple concurrently mounted Widget Instances. diff --git a/.scratch/wallet-service-runtime/issues/01-establish-scoped-wallet-runtime-contract.md b/.scratch/wallet-service-runtime/issues/01-establish-scoped-wallet-runtime-contract.md new file mode 100644 index 000000000..4dec1084b --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/01-establish-scoped-wallet-runtime-contract.md @@ -0,0 +1,18 @@ +# 01 — Establish the scoped Wallet Runtime contract + +**What to build:** Expand WalletService so one application-runtime generation captures one Wallet Bootstrap Snapshot, constructs one Wagmi config, and exposes the Wallet Runtime Phase, current snapshot, change stream, and authoritative config without disrupting existing wallet consumers. + +**Blocked by:** None — can start immediately. + +**Status:** ready-for-agent + +- [ ] WalletService captures the normalized bootstrap configuration, enabled networks, decoded initialization parameters, browser environment, and external-provider presence as one immutable Wallet Bootstrap Snapshot. +- [ ] WalletService constructs Wagmi no more than once for its scoped lifetime and retains one service and config identity. +- [ ] The public service contract exposes Bootstrapping, Ready, BootstrapFailed, and InvariantViolated phases together with current and changing Wallet Projections. +- [ ] Core connection and connector watchers are installed before their seed values are read, and Ready is not published until the initial canonical snapshot is available. +- [ ] Reconnect, mobile fallback, and initial chain switching run as scoped background work after core readiness, retaining their existing ordering and recoverable failure policy. +- [ ] Ordinary wallet-related configuration changes after bootstrap do not rebuild Wagmi or change Wallet Topology. +- [ ] Bootstrap construction failures publish a terminal BootstrapFailed phase without exposing a partially ready runtime. +- [ ] Disposing and remounting the application runtime releases the old resources and constructs a fresh Wallet Runtime. +- [ ] The WalletService contract tests exercise observable phases, snapshots, configuration identity, watcher ordering, background initialization, and cleanup without asserting private queue machinery. +- [ ] Existing wallet consumers continue to work while the new contract exists beside the legacy ownership path. diff --git a/.scratch/wallet-service-runtime/issues/02-build-headless-solana-runtime.md b/.scratch/wallet-service-runtime/issues/02-build-headless-solana-runtime.md new file mode 100644 index 000000000..5d5f4a70a --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/02-build-headless-solana-runtime.md @@ -0,0 +1,20 @@ +# 02 — Build the scoped headless Solana runtime + +**What to build:** Provide a scoped, non-React Solana runtime that constructs the RPC connection and maintains the available adapter descriptors with the discovery, readiness, fallback, mobile, and cleanup behavior required by the widget. + +**Blocked by:** None — can start immediately. + +**Status:** ready-for-agent + +- [ ] The runtime constructs one Solana Connection per Wallet Runtime with behavior equivalent to the existing provider configuration. +- [ ] The runtime uses the modern Wallet Standard registry and does not rely on the deprecated `navigator.wallets` registration path. +- [ ] Compatible wallets present at startup and wallets registered later are wrapped and published as adapter descriptors. +- [ ] Registry unregister events remove and dispose the corresponding wrapped adapters according to the Wallet Standard contract. +- [ ] Adapter readiness events update descriptors, and unsupported adapters are excluded. +- [ ] Same-name Standard adapters take precedence over inactive explicit fallbacks without producing duplicate wallet entries. +- [ ] Explicit Phantom, Trust, and WalletConnect fallbacks are instantiated once per scoped runtime. +- [ ] Android Mobile Wallet Adapter inclusion matches the existing environment, installed-wallet, endpoint, and app-identity behavior. +- [ ] All registry listeners, adapter listeners, wrappers, fallbacks, and mobile resources are released with the runtime scope. +- [ ] Production imports of Wallet Standard or adapter packages are declared as direct package dependencies. +- [ ] Deterministic tests cover initial discovery, late registration, unregister disposal, readiness, deduplication, unsupported filtering, Android behavior, and remount cleanup without real wallets or RPC traffic. +- [ ] This expand step does not yet replace the production Wagmi Solana connector path. diff --git a/.scratch/wallet-service-runtime/issues/03-make-service-wagmi-config-authoritative.md b/.scratch/wallet-service-runtime/issues/03-make-service-wagmi-config-authoritative.md new file mode 100644 index 000000000..8376877d3 --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/03-make-service-wagmi-config-authoritative.md @@ -0,0 +1,17 @@ +# 03 — Make the service-owned Wagmi config authoritative + +**What to build:** Route the React Wagmi boundary and the core connection/connector Wallet Projections through WalletService so the mounted widget uses one authoritative config and one source of reactive base Wallet State. + +**Blocked by:** 01 — Establish the scoped Wallet Runtime contract. + +**Status:** ready-for-agent + +- [ ] Wagmi's React context receives an inert fallback only while WalletService is bootstrapping and then receives the service-owned config by reference. +- [ ] No legacy controller or atom path constructs a second production Wagmi config after this migration. +- [ ] Core connection and connector changes are reduced by WalletService and exposed through read-only feature atoms/selectors. +- [ ] Existing feature hooks and UI observe connection, account, chain, and connector changes without writing canonical Wallet State. +- [ ] Equivalent rerenders and ordinary wallet-setting changes keep the same Wallet Runtime and Wagmi config. +- [ ] A new application-runtime generation constructs a fresh Wallet Runtime and config. +- [ ] Manual connect, disconnect, reconnect, and chain-switch actions continue to operate against the authoritative config. +- [ ] The browser contract verifies fallback-to-authoritative handoff, config identity, RainbowKit-facing actions, and read-only projection reactivity. +- [ ] Legacy enrichment and command bridges may remain temporarily only where later tickets still require them, and the test suite remains green. diff --git a/.scratch/wallet-service-runtime/issues/04-own-external-provider-snapshots.md b/.scratch/wallet-service-runtime/issues/04-own-external-provider-snapshots.md new file mode 100644 index 000000000..ac287501e --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/04-own-external-provider-snapshots.md @@ -0,0 +1,19 @@ +# 04 — Own External Provider Snapshot synchronization and invariants + +**What to build:** Make WalletService follow live external-provider values inside the Connector Mode chosen at bootstrap and fail deterministically when host updates would change that mode or produce an impossible connector state. + +**Blocked by:** 03 — Make the service-owned Wagmi config authoritative. + +**Status:** ready-for-agent + +- [ ] External-provider address, chain, supported chains, provider operations, and equivalent live values are read from a service-owned External Provider Snapshot. +- [ ] Live snapshot changes synchronize the external connector and connection without rebuilding Wagmi or rerunning Wallet Bootstrap. +- [ ] An available external address triggers the existing automatic connection behavior without duplicate concurrent attempts. +- [ ] Connector supported-chain, account, and chain notifications are deduplicated and ordered through the WalletService event loop. +- [ ] Connector Mode is fixed from the Wallet Bootstrap Snapshot. +- [ ] External-provider presence changing from absent to present or present to absent enters terminal InvariantViolated. +- [ ] A missing or mismatched external-provider connector in external-provider mode also enters InvariantViolated immediately. +- [ ] Each invariant is logged once, subsequent wallet work fails deterministically, and unrelated application services remain usable. +- [ ] A separate Wallet Runtime generation is unaffected by another generation's invariant violation. +- [ ] Service-level tests cover live value replacement, synchronization, both presence transitions, connector mismatch, log-once behavior, and scoped isolation. +- [ ] The legacy external-provider synchronization owner is no longer mounted after this behavior moves into WalletService. diff --git a/.scratch/wallet-service-runtime/issues/05-publish-complete-wallet-state.md b/.scratch/wallet-service-runtime/issues/05-publish-complete-wallet-state.md new file mode 100644 index 000000000..b7531d9d5 --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/05-publish-complete-wallet-state.md @@ -0,0 +1,17 @@ +# 05 — Publish complete canonical Wallet State + +**What to build:** Extend the service-owned event loop so WalletService publishes complete, atomic Wallet State—including connector chains, Ledger data, Cosmos routing, and additional addresses—while feature atoms become read-only Wallet Projections. + +**Blocked by:** 03 — Make the service-owned Wagmi config authoritative. + +**Status:** ready-for-agent + +- [ ] Canonical Wallet State includes the existing connection status, address, chain, network, connector, connector chains, Ledger account state, placeholder state, and validated additional addresses. +- [ ] Cosmos chain-wallet and other connector-specific command-routing objects remain private service context rather than public Wallet Projection fields. +- [ ] Connection and enrichment events are serialized so consumers observe complete snapshots rather than mixed old/new account, chain, or additional-address data. +- [ ] Recoverable connector-chain, Ledger, Cosmos, or additional-address failures degrade only their relevant slice and do not terminally fail a healthy Wallet Runtime. +- [ ] State changes are deduplicated and available through the service's current snapshot and changes stream. +- [ ] Existing wallet selectors, hooks, workflows, and Wallet Scope derivation retain their observable behavior through read-only adapters. +- [ ] Feature atoms cannot write canonical Wallet State or bind a projected value back into WalletService. +- [ ] Service contract tests drive controlled connection and enrichment events and assert only complete public snapshots plus required private command-routing outcomes. +- [ ] Obsolete state-owner tests are replaced by service and projection contract coverage while low-level protocol driver tests remain intact. diff --git a/.scratch/wallet-service-runtime/issues/06-move-wallet-lifecycle-policies.md b/.scratch/wallet-service-runtime/issues/06-move-wallet-lifecycle-policies.md new file mode 100644 index 000000000..a48497603 --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/06-move-wallet-lifecycle-policies.md @@ -0,0 +1,18 @@ +# 06 — Move wallet lifecycle policies into WalletService + +**What to build:** Run wallet connection tracking and unsupported-chain disconnect behavior as scoped WalletService lifecycle policies driven by canonical Wallet State. + +**Blocked by:** 03 — Make the service-owned Wagmi config authoritative. + +**Status:** ready-for-agent + +- [ ] Each distinct supported wallet connection emits the existing connected-wallet tracking event once. +- [ ] Equivalent Wallet State publications do not duplicate tracking. +- [ ] Leaving and later re-entering a connection resets deduplication so a genuinely new connection is tracked. +- [ ] Each distinct unsupported connection is disconnected once with the active connector. +- [ ] Equivalent unsupported snapshots do not trigger duplicate disconnect attempts. +- [ ] Returning to disconnected or supported state resets unsupported-connection deduplication. +- [ ] Tracking and disconnect failures preserve the current recoverable policy and do not poison the Wallet Runtime. +- [ ] Lifecycle effects consume the serialized service-owned Wallet State rather than a separately mounted state-owning atom. +- [ ] Lifecycle fibers and subscriptions stop when the Wallet Runtime scope is disposed. +- [ ] Service-level tests replace legacy lifecycle-owner tests and assert tracking/disconnect behavior through observable collaborators. diff --git a/.scratch/wallet-service-runtime/issues/07-route-commands-from-wallet-state.md b/.scratch/wallet-service-runtime/issues/07-route-commands-from-wallet-state.md new file mode 100644 index 000000000..6df53e505 --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/07-route-commands-from-wallet-state.md @@ -0,0 +1,18 @@ +# 07 — Route commands from captured Wallet State + +**What to build:** Make every WalletService command route directly from one captured service-owned state/context snapshot, fail immediately when unavailable, and remove the atom-to-service binding lifecycle. + +**Blocked by:** 04 — Own External Provider Snapshot synchronization and invariants; 05 — Publish complete canonical Wallet State; 06 — Move wallet lifecycle policies into WalletService. + +**Status:** ready-for-agent + +- [ ] Transaction signing, message signing, account switching, disconnect, and state reads resolve directly from WalletService-owned state and private routing context. +- [ ] Commands issued during Bootstrapping fail immediately with the appropriate typed failure and never wait on Deferred readiness. +- [ ] Commands issued after BootstrapFailed or InvariantViolated fail immediately and deterministically. +- [ ] An in-flight command continues against the state, controller operations, and connector-specific context captured when it began. +- [ ] A command begun after a Wallet State publication uses the newly published snapshot. +- [ ] EVM, external-provider, Safe, Ledger, Cosmos, Substrate, and miscellaneous routing retain their current success and typed-error behavior. +- [ ] Public command result types continue to distinguish signed payloads from broadcast transaction hashes. +- [ ] WalletBinding, the bind method, Deferred readiness, and the binding atom are removed from production ownership. +- [ ] Command contract tests replace binding-atom tests and cover active-command stability, next-command freshness, phase failures, and representative connector routing. +- [ ] No production command reads feature atom state. diff --git a/.scratch/wallet-service-runtime/issues/08-integrate-dynamic-solana-membership.md b/.scratch/wallet-service-runtime/issues/08-integrate-dynamic-solana-membership.md new file mode 100644 index 000000000..cddfa2aee --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/08-integrate-dynamic-solana-membership.md @@ -0,0 +1,20 @@ +# 08 — Integrate dynamic Solana membership into Wagmi + +**What to build:** Integrate the headless Solana runtime with the fixed Wallet Topology so dynamic Standard discovery and readiness update the existing Wagmi config and RainbowKit-facing hooks without hot-swapping an active adapter. + +**Blocked by:** 02 — Build the scoped headless Solana runtime; 03 — Make the service-owned Wagmi config authoritative; 04 — Own External Provider Snapshot synchronization and invariants. + +**Status:** ready-for-agent + +- [ ] WalletService supplies the headless runtime's Connection and initial Solana adapters when it constructs the one Wagmi config. +- [ ] The React Solana provider, Solana root input, layout-effect bridge, nullable connection race, and unsafe connection cast are removed from the production bootstrap path. +- [ ] A same-name Standard adapter replaces an inactive fallback without rebuilding the Wagmi config. +- [ ] Standard registration while the fallback is active is deferred, and disconnecting that fallback publishes the pending Standard adapter. +- [ ] Unregistering a pending Standard adapter keeps the active fallback; unregistering an inactive visible Standard adapter publishes the fallback. +- [ ] Unregistering an active Standard adapter disconnects and removes it before publishing the fallback. +- [ ] Readiness changes publish a fresh outer connector wrapper while preserving UID, emitter, methods, and underlying adapter identity. +- [ ] Core connector observers and React `useConnectors` consumers both observe membership and readiness changes. +- [ ] Volatile Wagmi connector-store access is encapsulated behind one service-owned membership adapter. +- [ ] External-provider Connector Mode does not accidentally acquire or expose unrelated Solana connectors. +- [ ] Browser characterization covers the prototype transition rules, same-UID readiness refresh, authoritative config identity, and RainbowKit-facing visibility. +- [ ] Runtime disposal removes discovery and readiness listeners without affecting a later remount. diff --git a/.scratch/wallet-service-runtime/issues/09-contract-legacy-wallet-ownership.md b/.scratch/wallet-service-runtime/issues/09-contract-legacy-wallet-ownership.md new file mode 100644 index 000000000..6426b40b9 --- /dev/null +++ b/.scratch/wallet-service-runtime/issues/09-contract-legacy-wallet-ownership.md @@ -0,0 +1,19 @@ +# 09 — Contract legacy wallet ownership and verify compatibility + +**What to build:** Remove the superseded controller, atom ownership, React bridge, and prototype paths after all consumers use WalletService, then verify the complete widget remains publicly and architecturally compatible. + +**Blocked by:** 06 — Move wallet lifecycle policies into WalletService; 07 — Route commands from captured Wallet State; 08 — Integrate dynamic Solana membership into Wagmi. + +**Status:** ready-for-agent + +- [ ] No production path retains initialization-key families, controller-owned Wagmi construction, WalletBinding, binding readiness, external-provider synchronization ownership, lifecycle ownership, or connection/connector/Ledger/Cosmos/additional-address state ownership outside WalletService. +- [ ] Feature atoms that remain are read-only Wallet Projections or selectors with no back-binding to the service. +- [ ] React composition retains only required third-party Wagmi/RainbowKit context boundaries and no longer owns Solana discovery or connection construction. +- [ ] Obsolete modules, exports, tests, dependencies, and imports are removed without deleting unrelated user work. +- [ ] The throwaway prototype command and production-branch prototype files are removed after their validated decisions are represented by tests and the specification. +- [ ] Published React component, bundled renderer, style, and existing wallet-facing API contracts remain compatible. +- [ ] Classic and dashboard wallet workflows continue to connect, disconnect, switch chains/accounts, and sign through the service-owned runtime. +- [ ] Ordinary configuration updates retain one Wallet Runtime, while an application-runtime remount receives clean service state and resources. +- [ ] The focused unit, DOM, and browser wallet suites pass. +- [ ] Package lint/type-check, formatting and hygiene checks, public API contracts, and the appropriate broader test suite pass. +- [ ] The final code follows the accepted WalletService ownership ADR and uses the glossary's Wallet Runtime terminology consistently. diff --git a/.scratch/wallet-service-runtime/spec.md b/.scratch/wallet-service-runtime/spec.md new file mode 100644 index 000000000..0646cdd38 --- /dev/null +++ b/.scratch/wallet-service-runtime/spec.md @@ -0,0 +1,159 @@ +# WalletService-owned Wallet Runtime + +Status: ready-for-agent + +## Problem Statement + +Wallet Bootstrap and Wallet State are currently distributed across keyed atoms, controller resources, React-fed Solana inputs, lifecycle atoms, and a WalletService that must be bound back to atom-owned state before its commands work. This creates multiple authorities for the same wallet, allows configuration changes to reconstruct Wagmi during a mounted widget generation, and makes command routing depend on whichever atom/controller binding is current. + +The widget needs one scoped owner for wallet initialization, state, effects, commands, discovery, and cleanup. A host should receive predictable behavior from the configuration and external provider available when a Wallet Runtime starts, while ordinary later configuration changes must not silently replace the underlying wallet system. The UI and workflows still need reactive wallet data, but they should consume read-only Wallet Projections rather than own or rebind canonical Wallet State. + +Solana is the most visible ownership leak: React providers currently construct the connection and discover wallets, then copy those values into an atom that participates in Wagmi initialization. Removing that bridge requires a headless runtime that preserves supported discovery, fallback, readiness, mobile, and connector behavior without rebuilding the Wagmi config. + +## Solution + +Make WalletService the sole owner of one Wallet Runtime per application-runtime generation. During Wallet Bootstrap it captures one immutable Wallet Bootstrap Snapshot, resolves the enabled networks and initialization inputs, constructs one Wagmi config, installs and seeds its core watchers, and publishes an atomic runtime snapshot. It then processes all wallet, connector, enrichment, external-provider, and discovery events through one serialized event loop. + +WalletService exposes its current runtime snapshot, a stream of changes, the service-owned Wagmi config, and wallet commands. Commands fail immediately until the Wallet Runtime is ready and capture one Wallet State snapshot for the duration of each operation. Feature atoms become read-only adapters and selectors over WalletService. React remains only as the adapter for third-party tree-scoped Wagmi and RainbowKit contracts. + +Wallet Topology remains fixed for the lifetime of the Wallet Runtime. Ordinary wallet-related configuration updates are ignored after bootstrap. External Provider Snapshot values remain live when the runtime started in external-provider Connector Mode, but adding or removing external-provider presence after bootstrap is a Wallet Runtime Invariant violation and terminally poisons only that Wallet Runtime. + +Solana connection construction and wallet discovery move into a scoped headless runtime. It uses the modern Wallet Standard registry, adapter readiness events, explicit fallbacks, Android Mobile Wallet Adapter parity, and dynamic connector membership within the already-created Wagmi config. Same-name Standard adapters take precedence, but an active fallback adapter is never hot-swapped. + +## User Stories + +1. As a widget host, I want wallet infrastructure to initialize once per mounted application generation, so that ordinary configuration rerenders cannot replace an active wallet runtime. +2. As a widget host, I want Wallet Bootstrap to use one atomic snapshot of current inputs, so that the runtime cannot combine values observed at different moments. +3. As a widget host, I want post-bootstrap wallet configuration changes to be ignored, so that the configured Wallet Topology remains stable. +4. As a widget host, I want a remounted application generation to receive a fresh Wallet Runtime, so that old listeners, adapters, connections, and state do not leak into the new mount. +5. As a widget host using an external provider, I want current address, current chain, supported chains, and provider operations to remain live, so that the widget follows the host wallet without rebuilding Wagmi. +6. As a widget host, I want adding or removing external-provider presence after bootstrap to fail visibly as an invariant violation, so that an invalid integration cannot continue in a mixed Connector Mode. +7. As a widget host, I want an external-provider connector mismatch to fail immediately and be logged once, so that impossible connector routing is diagnosed rather than silently ignored. +8. As a widget user, I want wallet connection state to remain reactive, so that UI and workflows reflect connector changes without owning those changes. +9. As a widget user, I want account and chain changes to update atom-backed UI consistently, so that every screen observes the same Wallet State. +10. As a widget user, I want wallet commands to use the state visible when the command begins, so that a concurrent account or connector update cannot reroute an in-flight signing operation. +11. As a widget user, I want commands issued before readiness to fail immediately, so that the UI never waits indefinitely for an internal binding. +12. As a widget user, I want commands issued after a terminal Wallet Runtime failure to return typed failures, so that the widget can surface a deterministic error. +13. As a widget user, I want reconnect behavior to remain unchanged, so that previously authorized wallets still reconnect when possible. +14. As a mobile widget user, I want the existing injected-provider fallback behavior to remain unchanged, so that mobile wallets still connect when reconnect finds no active connection. +15. As a widget user arriving with an initial chain parameter, I want the runtime to attempt the same initial switch, so that entry links retain their current behavior. +16. As a widget user, I want reconnect, mobile fallback, and initial switching failures to remain recoverable, so that manual wallet connection remains available. +17. As a widget user, I want unsupported connected chains to trigger the existing automatic disconnect policy, so that workflows do not operate against unsupported state. +18. As an analytics consumer, I want each supported wallet connection to be tracked once, so that moving lifecycle ownership does not duplicate connection events. +19. As a widget user, I want Ledger account state to remain part of the canonical Wallet State, so that account switching and signing stay coherent. +20. As a widget user, I want Cosmos chain-wallet routing to use the same captured Wallet State as other commands, so that transaction commands do not depend on separately published atoms. +21. As a widget user, I want additional addresses to update as part of an atomic Wallet State publication, so that workflows never combine a new account with stale enrichment. +22. As a widget user, I want a recoverable enrichment failure to degrade only that slice, so that a healthy wallet connection remains usable. +23. As a widget user, I want recoverable connector failures to affect only the relevant connector state, so that unrelated connectors remain available. +24. As a widget user, I want a bootstrap construction failure to produce a terminal BootstrapFailed phase, so that partially initialized wallet resources are never presented as ready. +25. As a widget user, I want Wallet State to become ready only after core Wagmi watchers are installed and seeded, so that the first ready snapshot cannot miss a concurrent event. +26. As a Phantom user, I want a discovered Wallet Standard adapter to take precedence over the explicit Phantom fallback, so that only one Phantom option is displayed. +27. As a Phantom user with an active fallback connection, I want late Standard discovery to be deferred, so that my active adapter is not hot-swapped. +28. As a Phantom user, I want the deferred Standard adapter to appear after I disconnect the fallback, so that the modern adapter becomes available without rebuilding the runtime. +29. As a user connected through a Standard adapter that unregisters, I want the runtime to disconnect it before exposing the fallback, so that connector membership and active state cannot disagree. +30. As a Solana user, I want readiness changes to update wallet availability without changing the underlying adapter, so that transient installation state does not reset my wallet integration. +31. As a Solana user, I want explicit Phantom, Trust, and WalletConnect fallbacks preserved, so that current wallet coverage remains available. +32. As an Android Solana user, I want Mobile Wallet Adapter behavior equivalent to the current React provider, so that removing React ownership does not remove mobile discovery. +33. As a Solana user, I want removed Wallet Standard wallets to be removed and disposed according to the registry contract, so that stale adapters do not remain visible. +34. As a browser-wallet user, I want late EVM provider discovery to update connector membership within the existing config, so that environmental discovery remains reactive. +35. As a RainbowKit consumer, I want dynamic connector membership and readiness to appear through the normal Wagmi hooks, so that the wallet UI needs no private discovery bridge. +36. As a React integrator, I want WagmiConfigProvider to expose the service-owned config after bootstrap, so that Wagmi and WalletService share one authority. +37. As a React integrator, I want an inert fallback config only during bootstrap, so that third-party hooks remain mount-safe before the real config exists. +38. As a feature developer, I want wallet atoms to be read-only Wallet Projections, so that feature code cannot accidentally become a second Wallet State owner. +39. As a feature developer, I want the existing feature-facing wallet selectors and hooks to retain their behavior, so that this ownership refactor does not require UI rewrites. +40. As a package consumer, I want the published React and bundled entry APIs to remain compatible, so that upgrading does not require integration changes. +41. As a maintainer, I want WalletService to expose a small behavioral interface, so that wallet orchestration can be tested without mounting every atom and provider. +42. As a maintainer, I want all listener, fiber, adapter, and registry resources scoped to WalletService, so that disposal is deterministic. +43. As a maintainer, I want volatile Wagmi internal connector-store access encapsulated behind one service-owned seam, so that a dependency upgrade has one repair point. +44. As a maintainer, I want the React Solana providers and root-input bridge removed, so that React is no longer an owner of wallet initialization. +45. As a maintainer, I want the legacy Wallet Standard registration path removed, so that the widget uses the modern registry contract only. +46. As a maintainer, I want one serialized wallet event loop, so that related connection, connector, enrichment, and lifecycle updates publish atomically and in order. +47. As a maintainer, I want private connector-specific routing context to remain outside the public Wallet Projection, so that consumers receive a minimal stable snapshot. +48. As a maintainer, I want one active widget per document to remain the supported lifecycle, so that this refactor does not imply unsupported global isolation guarantees. + +## Implementation Decisions + +- WalletService owns the complete scoped Wallet Runtime: Wallet Bootstrap, the Wagmi config, canonical Wallet State, connector streams, connector-specific routing context, external-provider synchronization, Solana discovery, tracking, unsupported-chain handling, commands, and cleanup. +- Each application-runtime generation constructs one fresh WalletService layer. All watchers, subscriptions, fibers, adapters, Wallet Standard registrations, and other acquired resources are released with that scope. +- WalletService captures one Wallet Bootstrap Snapshot from the current normalized widget configuration, enabled-network result, decoded initialization parameters, browser environment, and external-provider presence. It never reads atom-owned wallet state during or after bootstrap. +- The pure initialization-parameter decoder belongs in the domain layer and may be shared by WalletService and feature projections. Cross-feature initialization atoms may remain for routing and workflow concerns, but they do not own Wallet Bootstrap. +- Wallet Topology is fixed after bootstrap. Later ordinary wallet-related configuration publications do not reconstruct Wagmi, replace connectors selected by configuration, or rerun bootstrap. +- Environment-discovered connector membership may change within the fixed Wallet Topology. This includes browser-injected EVM providers and supported Solana discovery/readiness events. +- Connector Mode is selected once from the Wallet Bootstrap Snapshot. The runtime is either in external-provider mode or another configured connector mode; it cannot transition between modes. +- External Provider Snapshot values remain live through a service-owned current-value abstraction. Changing provider identity, account, chain, supported-chain data, or operations is allowed while external-provider presence remains stable. +- A transition from absent to present external-provider input, present to absent input, or an impossible external-provider connector mismatch is a Wallet Runtime Invariant violation. The service logs it once, enters InvariantViolated, stops accepting wallet work, and poisons only that Wallet Runtime. +- Runtime phases are Bootstrapping, Ready, BootstrapFailed, and InvariantViolated. Bootstrapping may transition to Ready or BootstrapFailed. Ready may transition to InvariantViolated. BootstrapFailed and InvariantViolated are terminal. +- BootstrapFailed is reserved for failures that prevent safe construction of the Wallet Runtime. Recoverable reconnect, initial switching, mobile fallback, connector, and enrichment failures do not produce BootstrapFailed. +- WalletService installs core connection and connector watchers before reading their seed values. Ready is published only after those watchers are active and the first canonical Wallet State has been reduced. +- Reconnect, mobile fallback, and initial requested-chain switching begin after core readiness as scoped background work. They preserve their current ordering and failure-tolerance policy and feed results through the same serialized event loop. +- All wallet-related events enter one serialized service-owned event loop. Each event updates private routing context and publishes one atomic runtime snapshot; consumers never observe partially combined connection, chain, account, Ledger, Cosmos, connector-chain, or additional-address state. +- The public runtime snapshot contains the Wallet Runtime Phase and the minimal Wallet Projection required by consumers. Raw Cosmos chain-wallet objects and other connector-specific command-routing details remain private. +- WalletService exposes the current snapshot and a deduplicated stream of subsequent snapshots. It also exposes the constructed Wagmi config once available and retains one service identity for the runtime generation. +- Commands fail immediately while Bootstrapping and after terminal failure; they never wait on Deferred readiness or an atom binding. Existing operation-specific tagged errors remain the public failure vocabulary, extended only where a distinct runtime-phase failure is necessary. +- Every command captures one canonical state and routing-context snapshot when it begins. An in-flight command completes against that captured snapshot even if later events publish a new Wallet State; the next command uses the new snapshot. +- Transaction signing, message signing, account switching, disconnect, state reads, and connector-specific routing resolve directly from WalletService-owned state. No command reads binding-atom state. +- Tracking a supported connection and disconnecting an unsupported connection move into WalletService lifecycle handling. Both behaviors retain connection-key deduplication and current error-swallowing policy where applicable. +- Atoms become read-only adapters over the WalletService current snapshot and changes stream. They may derive feature-friendly values but cannot write canonical Wallet State or bind it back to the service. +- Initialization-key families, the wallet controller resource, WalletBinding, bind/Deferred readiness, lifecycle ownership atoms, external-provider synchronization atoms, and connection/connector/Ledger/Cosmos/additional-address state ownership atoms are removed or reduced to read-only projections as appropriate. +- WagmiConfigProvider keeps the third-party React context boundary. It provides an inert fallback during Bootstrapping, then provides the WalletService-owned Wagmi config by reference. The service config is not replaced for ordinary configuration changes. +- The Solana runtime constructs the RPC Connection directly and synchronously with behavior equivalent to the current provider. The React Solana provider, Solana root input, layout-effect bridge, nullable connection race, and unsafe connection cast are removed. +- The Solana runtime uses the modern Wallet Standard `getWallets()` registry only. It does not preserve deprecated `navigator.wallets` registration compatibility. +- Wallet Standard and Solana wallet-adapter packages imported by production code become direct package dependencies rather than relying on transitive dependencies. +- The scoped Solana runtime instantiates fallback adapters once per Wallet Runtime, wraps compatible registered Standard wallets, follows registry register/unregister events, subscribes to adapter readiness events, removes and disposes unregistered wrappers, deduplicates by wallet name, filters unsupported adapters, and preserves Android Mobile Wallet Adapter environment behavior. +- Explicit Phantom, Trust, WalletConnect, and Android Mobile Wallet Adapter fallbacks remain. A compatible Standard adapter wins over an inactive same-name fallback. +- The connector-membership transitions validated by the prototype are normative: + + | Event | Active adapter | Required result | + | --- | --- | --- | + | Same-name Standard registers | None or unrelated | Publish Standard instead of fallback | + | Same-name Standard registers | Fallback | Keep fallback active and mark Standard pending | + | Active fallback disconnects | Standard pending | Publish the pending Standard | + | Pending Standard unregisters | Fallback | Discard pending Standard and keep fallback | + | Visible inactive Standard unregisters | None | Publish fallback | + | Active Standard unregisters | Standard | Disconnect Standard, remove it, then publish fallback | + +- An adapter readiness change does not rebuild Wagmi and does not replace the underlying adapter. The prototype established that mutating nested RainbowKit metadata alone does not wake React consumers because Wagmi reuses the previous connector array when all outer connector objects are identical. +- To publish readiness, the service creates a fresh outer connector wrapper with updated metadata while preserving its UID, emitter, methods, and underlying adapter, then republishes connector membership through the existing config. This is wrapper refresh, not an active adapter hot-swap. +- Direct access to Wagmi's internal connector store is encapsulated behind one WalletService-owned connector-membership adapter. No feature atom, React provider, or unrelated connector may depend on that internal API. +- Public package exports and current feature-facing wallet behavior remain compatible. Internal wallet atoms, controllers, bindings, and helpers are not public compatibility constraints and may be removed. +- Production code continues to support one active widget instance per document and clean sequential unmount/remount. Concurrent widget support is not introduced. + +## Testing Decisions + +- The primary test seam is the scoped WalletService public contract. Tests construct the service with controllable bootstrap, environment, Wagmi, registry, tracking, and persistence dependencies; drive events through those dependencies; and assert only runtime phases, current snapshots, change streams, commands, lifecycle effects, invariants, and disposal. +- WalletService tests cover one-time bootstrap, atomic snapshot capture, ignored post-bootstrap configuration, service identity stability, scoped remount isolation, listener cleanup, watcher-before-seed ordering, and terminal bootstrap failure. +- WalletService tests cover serialized state publication across connection, connector, chain, account, Ledger, Cosmos, connector-chain, and additional-address changes. They assert complete observable snapshots rather than private reducer steps. +- Command tests cover immediate pre-ready failure, terminal-phase failure, snapshot capture for in-flight commands, next-command use of newly published state, and coherent routing for signing, disconnect, and account switching. +- External-provider tests cover live address/chain/supported-chain/provider changes, automatic connection synchronization, presence changes in both directions, connector mismatch invariants, log-once behavior, and isolation of the failed Wallet Runtime. +- Lifecycle tests cover connection-event deduplication, unsupported-chain disconnect deduplication, recoverable failures, and reset behavior after returning to a supported/disconnected state. +- Solana headless-runtime tests drive a fake modern Wallet Standard registry and fake adapters. They cover initial discovery, register/unregister, correct unregister disposal, name deduplication, unsupported filtering, readiness events, fallback preservation, Android Mobile Wallet Adapter conditions, and runtime-scope cleanup. +- Connector-membership tests cover every normative transition in the prototype table, including deferred same-name replacement and disconnect-before-fallback ordering. Assertions use visible connector membership, active connection, UID stability, and disposal effects rather than the private state-machine representation. +- One narrow browser contract evolves the existing WagmiConfigProvider/RainbowKit-facing suite. It verifies fallback-only-during-bootstrap behavior, service-owned config identity, no config replacement after ordinary settings changes, connection actions against the authoritative config, dynamic Solana membership, and readiness propagation through `useConnectors` with a preserved UID. +- The same browser contract includes a Wallet Projection probe so that the service snapshot, read-only atom adapter, and Wagmi-facing hooks are observed together at the integration boundary. Separate tests for deleted binding/controller/state-owning atoms are removed. +- Existing low-level driver and connector tests remain for protocol-specific transaction decoding, signing, broadcasting, account switching, and provider error normalization. They should not duplicate Wallet Runtime orchestration cases. +- Existing runtime-generation tests remain prior art for scoped service identity, cleanup, and remount behavior. Existing WalletService contract tests are prior art for typed Effect commands. Existing Wagmi provider browser tests are prior art for authoritative config identity and RainbowKit-facing actions. +- Good tests assert externally observable behavior and lifecycle ordering. They do not assert private event-loop queue types, internal atom families, connector-wrapper construction helpers, or exact module layout. +- Tests use deterministic fake registries, adapters, providers, and operations. They do not require installed browser wallets, real RPC endpoints, network access, or timing-dependent global discovery. +- Public API and hygiene checks must continue to pass. Services remain free of React dependencies and preserve feature-to-service dependency direction as documented design constraints. + +## Out of Scope + +- Migrating from legacy `@solana/web3.js` transaction and Connection types to `@solana/client`, `@solana/web3-compat`, or another Solana SDK. +- Adopting `@solana/connector/headless` or replacing the widget's existing adapter contract with ConnectorKit. +- Preserving the deprecated `navigator.wallets` registration path. +- Reconfiguring Wallet Topology in place after Wallet Bootstrap. +- Supporting a runtime transition into or out of external-provider Connector Mode. +- Hot-swapping an active underlying wallet adapter. +- Supporting multiple concurrent widget instances in one document. +- Redesigning wallet modals, connect buttons, account UI, or user-facing copy. +- Changing published package entrypoints or intentionally breaking public React/bundle integrations. +- Persisting Wallet State beyond the existing persistence contracts. +- Shipping the prototype terminal application as production or test code. + +## Further Notes + +- This specification implements the accepted decision that WalletService owns the Wallet Runtime and follows the repository glossary's Wallet Runtime, Wallet Bootstrap Snapshot, Wallet Topology, Connector Mode, Wallet State, Wallet Projection, External Provider Snapshot, and Wallet Runtime Invariant terminology. +- The Solana research establishes that direct Connection construction, the modern Wallet Standard registry, and adapter readiness events are available without React. It also identifies the installed unregister-handler behavior that must not be copied. +- The connector-membership prototype is a primary-source experiment. It demonstrated real Wagmi core and React observation behavior and discovered the outer-wrapper requirement for readiness updates. Capture it outside the production branch according to the prototype workflow after its decisions are absorbed. +- Access to Wagmi's internal connector store is the main dependency risk. The implementation should characterize that boundary and keep it narrow enough to replace if Wagmi changes its internal API. +- The next planning step is to split this spec into blockers-first tracer-bullet tickets. Each ticket should leave the widget in a working state and should identify which obsolete ownership path it can safely remove. diff --git a/.vscode/settings.json b/.vscode/settings.json index 49e9f9c6e..4e103a2a1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,16 @@ { - "typescript.preferences.importModuleSpecifier": "relative", + "js/ts.preferences.importModuleSpecifier": "relative", + "js/ts.preferences.autoImportFileExcludePatterns": ["@repos/**"], "editor.codeActionsOnSave": { "source.organizeImports.biome": "explicit" }, - - "typescript.preferences.autoImportFileExcludePatterns": ["repos/**"], - "javascript.preferences.autoImportFileExcludePatterns": ["repos/**"], - "files.exclude": { - "repos/**": true + "@repos/**": true }, - "files.watcherExclude": { - "repos/**": true + "@repos/**": true }, - "search.exclude": { - "repos/**": true + "@repos/**": true } } diff --git a/AGENTS.md b/AGENTS.md index 57d739464..ec4215dea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,8 @@ - React component export (`src/index.package.ts`) - Fully bundled renderer (`src/index.bundle.ts`) - Runtime branches between classic widget and dashboard variant in `src/App.tsx`. +- Support at most one concurrently mounted Widget Instance per browser document. Sequential unmount and remount is supported. +- Do not preserve or introduce isolation and concurrency machinery solely for multiple Widget Instances. Keep concurrency control required within the single supported Widget Instance. ## Repo Layout (important paths) - `packages/widget/src/App.tsx` — root app, router setup, bundle renderer. @@ -16,24 +18,54 @@ - `packages/widget/src/hooks/*` — feature and API hooks. - `packages/widget/src/domain/*` — shared domain types/helpers. - `packages/widget/src/translation/*` — i18n resources (`English`, `French`). -- `packages/widget/tests/*` — Vitest browser tests (MSW-backed). +- `packages/widget/tests/*` — Vitest Node and browser tests; browser tests use the `.browser.test.*` suffix and MSW. - `packages/examples/*` — integration examples (`with-vite`, `with-vite-bundled`, `with-nextjs`, `with-cdn-script`). ## Commands Agents Should Use +### Package manager and dependency installation + +- Run all pnpm commands through the version pinned by mise: `mise exec -- pnpm ...`. +- Do not install dependencies unless a dependency manifest or lockfile changed, `node_modules` is missing or invalid, or the requested work otherwise requires it. +- When sandboxed, request approval to run dependency-installing or dependency-modifying commands outside the sandbox so pnpm can use the normal global store. +- Do not fall back to a sandboxed install or create a project-local `.pnpm-store`. If approval is denied, report the blocked installation instead of changing the store configuration. + ### From repo root (all workspaces via Turbo) -- `pnpm build` — build all packages. -- `pnpm lint` — lint/type-check all packages. -- `pnpm test` — run all workspace tests. -- `pnpm format` — run formatting checks/tasks. -- `pnpm check-hygiene` — check unused deps, unresolved imports, circular deps, etc. + +- `mise exec -- pnpm build` — build all packages. +- `mise exec -- pnpm lint` — lint/type-check all packages. +- `mise exec -- pnpm test` — run all workspace tests. +- `mise exec -- pnpm format` — run formatting checks/tasks. +- `mise exec -- pnpm check-hygiene` — check unused deps, unresolved imports, circular deps, etc. ### Focused widget commands (recommended for most tasks) -- `pnpm --filter @stakekit/widget {command}` + +- `mise exec -- pnpm --filter @stakekit/widget {command}` +- `mise exec -- pnpm --filter @stakekit/widget test:unit` — run the fast Node test project. +- `mise exec -- pnpm --filter @stakekit/widget test:dom` — run React/DOM tests in jsdom. +- `mise exec -- pnpm --filter @stakekit/widget test:browser` — run the Chromium test project. +- `mise exec -- pnpm --filter @stakekit/widget test:changed` — run affected Node + jsdom tests. +- `mise exec -- pnpm --filter @stakekit/widget test:changed:all` — run all affected projects, including Chromium. ## Agent Working Guidelines (short) - Keep public API compatibility in `src/index.package.ts` and `src/index.bundle.ts`. - React Compiler is enabled. Do not add `useMemo`, `useCallback`, or `React.memo` only for render-performance optimization; prefer plain values/functions. +- Do not use nested ternaries or mutable bindings for value selection. Prefer immutable `const` results from pure resolvers for boolean precedence and Effect `Match` for closed domain alternatives or clear multi-branch projections. +- Treat React as the view layer. Put new or materially refactored business state, transitions, asynchronous work, retries, concurrency, and resource lifetimes in Effect and Effect Atom; React should read Atom state and dispatch user intent. +- Use `useEffect` only for unavoidable React, DOM, or third-party lifecycle boundaries that cannot be expressed safely with scoped Effects or lifecycle Atoms. Do not use it for data fetching, duplicated-state synchronization, workflow advancement, or domain-resource cleanup. +- Local synchronous presentation state may remain in React when it has no domain meaning, asynchronous behavior, persistence, route lifetime, or cross-component coordination, such as focus, hover, disclosure, or element refs. +- React may mount a lifecycle Atom when a resource follows view or route visibility, but acquisition, interruption, and finalization stay inside Atom/Effect. Widget-runtime resources must be scoped to the runtime rather than component effects. +- Keep React event handlers synchronous: normalize the UI event and dispatch an Atom command. Do not call `Effect.runPromise`, await asynchronous work, coordinate retries or state transitions, or clean up domain resources in the handler. +- Keep deterministic domain constructors, transitions, invariant checks, and projections as plain TypeScript. Use Atom for reactive state and commands, and Effect for typed asynchronous work, dependencies, concurrency, and scoped resources. +- Represent application instants with Effect `DateTime`, intervals with `Duration`, and effectful current time with `Clock` or `DateTime.now`; do not use native `Date`, native-Date Effect schemas/conversions, or `date-fns`. Follow `docs/adr/0010-effect-datetime-owns-application-time.md` for boundary tolerance, presentation buckets, and the documented synchronous adapter exception. +- Feature facades should expose read-only view Atoms and writable command Atoms while keeping mutable storage private. React convenience hooks must be zero-logic adapters rather than places for derivation, variant branching, or orchestration. +- Effect-backed resources and command Atoms own loading, typed failure normalization, retry eligibility, and stale-result suppression. React renders the published state and dispatches Retry; it does not catch promises, normalize raw errors, or maintain duplicate loading flags. +- Treat these as review constraints: application-logic modules must not import React, and touched view adapters must not use `useEffect`. Any unavoidable external lifecycle exception must be isolated in a named boundary adapter and explicitly documented. +- Prefer a scoped Effect exposed through an Atom lifecycle for the document-level Widget Instance claim. If React mount semantics require a hook bridge, isolate and document one embedding-boundary hook that only acquires/releases the claim and contains no wallet or feature logic. +- Do not introduce React Query, hook-owned fetches, or Promise caches for new or materially refactored feature resources. Use Effect services/resources exposed through Atom; leave unrelated existing React Query usage unchanged unless it is in scope. +- Run feature Effects through the existing scoped application or wallet Atom runtimes and injected Effect services. Feature code must not create ad hoc runtimes or call `Effect.runPromise`; runtime generations own interruption and cleanup. +- Prefer headless Effect services over React-only third-party APIs. When no headless API exists, isolate the hook in a named boundary adapter that normalizes external values/callbacks into Atom; keep decisions, sequencing, errors, and non-library cleanup in Effect/Atom. +- Classic Transaction Flow code follows `packages/widget/src/features/classic-transaction-flow/ARCHITECTURE.md`. Re-review any external boundary when its lifecycle responsibilities change. - When changing user-facing copy, update both: - `packages/widget/src/translation/English/translations.json` - `packages/widget/src/translation/French/translations.json` @@ -44,7 +76,7 @@ - React Query defaults are in `packages/widget/src/providers/query-client/index.tsx`. - App-level config/env mapping is in `packages/widget/src/config/index.ts`. - Test bootstrapping + MSW worker setup: - - `packages/widget/tests/utils/setup.ts` + - `packages/widget/tests/utils/setup.browser.ts` - `packages/widget/tests/mocks/worker.ts` ## Vendored Repositories @@ -60,3 +92,17 @@ This project vendors external repositories under `@repos/`. - Before writing any Effect code, inspect `@repos/effect/LLMS.md` - Before writing code that interacts with Effect `HttpClient`, inspect `agent-patterns/effect-http-client.md` - Before writing code that uses Effect `Stream`, inspect `agent-patterns/effect-stream.md` + +## Agent skills + +### Issue tracker + +Issues are tracked as local Markdown files under `.scratch/`. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Triage state uses the five default canonical role strings. See `docs/agents/triage-labels.md`. + +### Domain docs + +Domain documentation uses a single-context layout. See `docs/agents/domain.md`. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 000000000..967a88de8 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,120 @@ +# StakeKit Widget + +StakeKit Widget embeds staking and related wallet workflows into a host application. Its wallet context connects host-provided configuration and providers to the accounts and networks available within one widget instance. + +## Embedding Language + +**Widget Instance**: +A mounted StakeKit Widget within a browser document. A document may contain at most one Widget Instance at a time; unmounting it and later mounting another is supported. +_Avoid_: Concurrent widgets, multiple widget instances + +**Application Runtime Generation**: +One continuous lifetime of widget application state under a stable API identity. It ends when that identity changes or the Widget Instance unmounts; live settings changes remain within the same generation. +_Avoid_: Widget Runtime, app mount + +## Wallet Language + +**Wallet Scope**: +An immutable snapshot of a connected wallet's network, primary address, and relevant additional addresses for execution inputs. Its owner identity is the network plus primary address (with case-insensitive EVM address comparison); a prepared action can enter execution only when its owner matches the captured Wallet Scope, and disconnecting or changing that owner invalidates Wallet Scope-bound flows while additional-address changes alone do not. +_Avoid_: Connector scope + +**Wallet Runtime**: +The wallet capabilities and state belonging to one widget application generation. Each generation has an isolated lifetime. + +**Wallet Runtime Invariant**: +A condition that must remain true for a Wallet Runtime to safely use its fixed Wallet Topology. A violation indicates an invalid host integration rather than an operational wallet failure. + +**Wallet Bootstrap**: +The one-time establishment of Wallet Topology, initial connection behavior, and first Wallet State for a Wallet Runtime. The Wallet Runtime is unavailable until Wallet Bootstrap completes. +_Avoid_: Wallet setup, wallet initialization + +**Wallet Bootstrap Snapshot**: +The immutable set of inputs captured together when Wallet Bootstrap begins. It determines Wallet Topology for the lifetime of the Wallet Runtime. +_Avoid_: Current configuration + +**Wallet Topology**: +The config-derived chain set, Connector Mode, and connector construction policy of a Wallet Runtime. It is fixed at bootstrap, while environment-discovered connector membership and readiness may change within that policy. +_Avoid_: Wallet configuration + +**Connector Mode**: +The mutually exclusive source of connectors selected during Wallet Bootstrap. A Wallet Runtime remains in its initial Connector Mode for its entire lifetime. +_Avoid_: Connector scope, wallet scope + +**Wallet State**: +The authoritative current connection, account, chain, and connector-specific details of a Wallet Runtime. Consumers receive Wallet State read-only; `WalletService` owns its changes. + +**External Provider Snapshot**: +The latest host-supplied external wallet identity, supported chains, and wallet operations. It may be replaced during a Wallet Runtime without changing Wallet Topology. +_Avoid_: External provider configuration + +## Earn Language + +**Earn Selection**: +The category, token, yield, validators, and entry form values currently resolved for starting an Earn journey. It is valid only against the authoritative facts for the active Wallet Scope Owner. +_Avoid_: Atom state, selected stake data + +**Yield Entry**: +A user's pre-execution attempt to add tokens to an Earn Selection. An eligible Yield Entry culminates in an Enter Action Command. +_Avoid_: Enter Action, stake form + +**Earn Initialization**: +The one-time use of host or deep-link initialization parameters to seed the first Earn Selection of a Widget Instance. A consumed or invalidated initialization target does not run again after user intent or a Wallet Scope Owner change. +_Avoid_: Permanent default, init fallback + +**Earn Readiness**: +The condition in which every authoritative fact needed to resolve the Earn Selection and determine whether it may be submitted has settled with a usable value. Pagination and later refreshes may continue without ending Earn Readiness. +_Avoid_: Page loaded, no spinner + +**Earn Mechanic Arguments**: +The yield-advertised action inputs whose constraints and options determine additional Earn form and transaction values. Only arguments understood by the Widget participate in Earn Selection. +_Avoid_: Raw mechanic fields, yield contract + +**Wallet Scope Owner**: +The owner identity of a Wallet Scope, consisting only of its network and primary address. Additional-address changes do not change the Wallet Scope Owner. +_Avoid_: Wallet Scope key, connector identity + +## Transaction Flow Language + +**Transaction Flow**: +A Wallet Scope-bound user journey through Review, Steps, and Complete, owned by one fresh Flow Session. Classic and Borrow Transaction Flows share this lifecycle language while retaining distinct intake, action preparation, and execution behavior. +_Avoid_: Transaction Workflow, shared flow implementation + +**Classic Transaction Flow**: +The Wallet Scope-bound journey from action review to execution handoff. It has Enter, Exit, Manage, and Activity Resume variants; a widget instance owns at most one active Flow Session, whose captured intake facts remain immutable for that entire journey. +_Avoid_: Classic transaction request + +**Borrow Transaction Flow**: +The Wallet Scope-bound journey from prepared borrow-action intake through Review, action creation, execution, and Complete. Borrow market and position actions enter through the same flow while retaining their distinct immutable intake facts. +_Avoid_: Borrow workflow, borrow dashboard flow + +**Flow Session**: +One user attempt to complete a Transaction Flow. Every explicit Start creates a fresh Flow Session even when its intake facts equal those of another attempt; Review, Steps, and Complete share its immutable intake and Wallet Scope until the entire journey is exited or replaced. +_Avoid_: Transaction Flow Identity, request object identity + +**Action Command**: +The prepared instruction describing the yield action the user intends to perform before that action is created. +_Avoid_: Request DTO + +**Action Preview**: +A freshly prepared Yield Action candidate derived from the Flow Session intake and inspected during Review. Continuing promotes that candidate into one Execution attempt; returning to Review always requires a fresh candidate. +_Avoid_: Attached action, prepared action + +**Yield Action**: +The created yield action containing the transactions required to carry out an Action Command. A Flow Session hands at most one reviewed Yield Action into its current Execution attempt. +_Avoid_: Action DTO + +**Execution Attempt**: +The Steps-and-Complete portion of a Flow Session for one reviewed action. It owns one Transaction Workflow and ends permanently when execution is left; a later execution always creates a fresh attempt. +_Avoid_: Executable phase, attached action + +**Transaction Workflow**: +A single execution of a prepared transaction plan, covering signing, submission, confirmation, multi-step advancement, retry, and completion. It is fresh each time an action enters execution and ends permanently when that execution is left, even when a later execution has equal inputs. +_Avoid_: Workflow identity, resumable workflow, workflow family + +**Classic Transaction Flow Abandonment**: +The end of an active Flow Session when its journey is exited, its Wallet Scope no longer matches, or a new session begins. Returning from execution to Review ends only the current Execution Attempt and does not abandon the Flow Session. +_Avoid_: Request cleanup + +**Activity Resume**: +A Classic Transaction Flow started from a Yield Action selected in activity history. Review reconstructs a fresh Action Preview when the historical action contains enough intake; unsupported historical actions remain non-executable. +_Avoid_: Activity selection diff --git a/README.md b/README.md index cd1271736..13eade366 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ pnpm add @stakekit/widget To use Widget, first you'll need API key from Yield.xyz. +### Supported widget instances + +Only one StakeKit Widget may be mounted in a browser document at a time. Unmounting it and later mounting another is supported; concurrently mounted Widget Instances are not supported. + ## React component usage After you get the API key, you can import styles and widget component: @@ -43,7 +47,7 @@ const App = () => { import "@stakekit/widget/bundle/css"; import { renderSKWidget, lightTheme, darkTheme } from "@stakekit/widget/bundle"; -const { rerender } = renderSKWidget({ +const { rerender, unmount } = renderSKWidget({ container: document.getElementById("sk_widget_container")!, apiKey: "your-api-key", theme: lightTheme, @@ -53,6 +57,8 @@ rerender({ apiKey: "your-api-key", theme: darkTheme, }) // pass new props here + +unmount() // release the document so another Widget Instance may be mounted ``` ## Params diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..b71b5be9e --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +- remove useEffect/useLayoutEffect +- move non-view logic to atoms +- docs cleanup \ No newline at end of file diff --git a/agent-patterns/README.md b/agent-patterns/README.md new file mode 100644 index 000000000..edbd0e9de --- /dev/null +++ b/agent-patterns/README.md @@ -0,0 +1,47 @@ +# Effect Agent Patterns + +These guides describe the Effect APIs used by this repository. They target the +vendored Effect snapshot under `@repos/effect`; unstable APIs can change, so the +vendored source wins whenever a guide and a signature disagree. + +## Pick The Smallest Relevant Guide + +| Work involves | Read | +| --- | --- | +| Reactive state, async resources, hydration, or atom lifetimes | `effect-atoms.md` | +| Outgoing HTTP requests, generated clients, retries, or response bodies | `effect-http-client.md` | +| Polling, pagination, callback sources, queues, or incremental processing | `effect-stream.md` | + +Read more than one guide when a boundary crosses modules. In particular: + +- An HTTP response body exposed as a stream needs both the HTTP and Stream + guides. +- An atom backed by a stream needs both the Atom and Stream guides. +- An atom-backed HTTP query needs all three when it incrementally consumes the + response. + +## Source-Checking Workflow + +Before writing Effect code: + +1. Read `@repos/effect/LLMS.md`. +2. Read the relevant guide from this directory. +3. Inspect the exact exported signature and nearby implementation in + `@repos/effect/packages`. +4. Inspect the corresponding vendored tests for lifecycle or concurrency + behavior that the type signature cannot express. + +Use `rg --no-ignore` when searching `@repos/effect`, because the vendored clone +may be ignored by Git locally. Do not use `node_modules` or web documentation as +the authority for Effect behavior in this repository. + +## Repository Rules Still Apply + +- Match nearby imports and project abstractions before introducing a new one. +- Keep generated API clients generated. Put hand-written behavior in transport + or service layers rather than editing generated files. +- Prefer `Effect.gen` and named `Effect.fn` functions for domain operations. +- Keep service requirements in the type until the application boundary provides + the layer. +- Test interruption, finalization, and time-dependent behavior when they are part + of correctness, not only the happy-path value. diff --git a/agent-patterns/effect-atoms.md b/agent-patterns/effect-atoms.md index e0b801e4b..fcaefeb94 100644 --- a/agent-patterns/effect-atoms.md +++ b/agent-patterns/effect-atoms.md @@ -1,28 +1,51 @@ # Effect Atom Patterns -Use this when writing or changing code that uses Effect's unstable atom -reactivity APIs. The source of truth reviewed for these patterns is the vendored -Effect repo: `@repos/effect/LLMS.md`, -`@repos/effect/packages/effect/src/unstable/reactivity/Atom.ts`, -`AtomRegistry.ts`, `AsyncResult.ts`, `Reactivity.ts`, `Hydration.ts`, -`AtomHttpApi.ts`, `AtomRpc.ts`, and the tests in -`@repos/effect/packages/effect/test/reactivity`. +Use this guide for Effect's unstable reactivity APIs: local state, derived +state, async resources, mutations, pagination, invalidation, persistence, and +hydration. + +The source of truth is the vendored Effect repository, especially: + +- `@repos/effect/packages/effect/src/unstable/reactivity/Atom.ts` +- `@repos/effect/packages/effect/src/unstable/reactivity/AtomRegistry.ts` +- `@repos/effect/packages/effect/src/unstable/reactivity/AsyncResult.ts` +- `@repos/effect/packages/effect/src/unstable/reactivity/Reactivity.ts` +- `@repos/effect/packages/effect/src/unstable/reactivity/Hydration.ts` +- `@repos/effect/packages/effect/src/unstable/reactivity/AtomHttpApi.ts` +- `@repos/effect/packages/effect/src/unstable/reactivity/AtomRpc.ts` +- `@repos/effect/packages/effect/test/reactivity` ## Imports -Import atom APIs from the unstable reactivity barrel. +Match nearby code. The reactivity barrel is convenient when several modules are +used together; module imports are useful for namespace-style or type-only +imports. ```ts -import { Effect, Layer, Schema, Stream } from "effect" -import { AsyncResult, Atom, AtomRegistry, Hydration } from "effect/unstable/reactivity" +import { Effect, Option, Schema, Stream } from "effect" +import { + AsyncResult, + Atom, + AtomRegistry, + Hydration +} from "effect/unstable/reactivity" ``` -## Registry Owns State +## Mental Model + +An `Atom` is a stable description of how to read or write a value. An +`AtomRegistry` owns the mutable runtime state: -An `Atom` is a value description. An `AtomRegistry` owns cached values, -dependency edges, subscriptions, running fibers, stream scopes, and disposal. -Use one registry per isolated lifetime: UI root, request, route boundary, or -test. +- cached values and serializable preloads +- parent/child dependency edges +- subscriptions and mounted nodes +- running fibers, stream scopes, and finalizers +- idle timers and disposal + +The same atom can hold different values in different registries. Use one +registry per isolated lifetime, such as a UI root or test. Prefer +`AtomRegistry.layer` when an Effect scope owns that lifetime; its finalizer +disposes the registry. ```ts const count = Atom.make(0) @@ -33,31 +56,25 @@ registry.set(count, 21) registry.get(doubled) // 42 ``` -The same atom object can have different values in different registries. After -`registry.dispose()`, later atom access is an error. +A bare `registry.get(atom)` does not mount the atom. An unobserved node is +eligible for disposal on a later scheduler turn. This matters for async work: +mount or subscribe when the computation must remain alive. -## Keep Atom Identity Stable +After `registry.dispose()`, creating or accessing nodes throws. `reset()` clears +nodes but leaves the registry reusable; `dispose()` is terminal. -Do not create parameterized atoms inline during reads or renders. Atom identity -is the cache key unless the atom is serializable. Use `Atom.family` for -parameterized atoms so the same input returns the same atom object. +## Choose The Right Shape -```ts -const userAtom = Atom.family((id: string) => - UserClient.runtime.atom(UserClient.use((client) => client.getUser({ id }))).pipe( - Atom.withLabel(`user:${id}`) - ) -) -``` - -Use `Atom.withLabel` on important atoms. It only adds diagnostic metadata and -does not change behavior. - -## Choose The Right Constructor - -Use `Atom.make(value)` for writable local state, `Atom.make((get) => value)` for -derived synchronous state, and `Atom.make(effectOrStream)` for read atoms that -produce `AsyncResult`. +| Need | API | Exposed value | +| --- | --- | --- | +| Writable local state | `Atom.make(initial)` | `A` | +| Synchronous derivation | `Atom.make((get) => value)` | `A` | +| Async or stream resource | `Atom.make(effectOrStream)` | `AsyncResult` | +| Synchronous command | `Atom.fnSync` | `Option` or configured initial `A` | +| Async command/mutation | `Atom.fn` | `AsyncResult` | +| Incremental page/chunk loading | `Atom.pull` | `PullResult` | +| Existing mutable Effect state | `Atom.subscriptionRef` | writable resource atom | +| Parameterized atom | `Atom.family` | same shape returned by the family | ```ts const search = Atom.make("") @@ -72,57 +89,97 @@ const users = Atom.make((get) => ) ``` -Use `Atom.fn` for command-style effects that run when written to. Use -`Atom.fnSync` for synchronous commands. Before the first write, `Atom.fn` -returns `AsyncResult.initial()` unless `initialValue` is supplied, and -`Atom.fnSync` returns `Option.none()` unless `initialValue` is supplied. +Use `Atom.fn` for work that starts only when a value is written. Before the +first write it is `AsyncResult.initial()` unless `initialValue` is supplied. +The default latest write wins: refreshing the command atom disposes its prior +lifetime and interrupts its computation. ```ts -const saveUser = Atom.fn<{ readonly id: string; readonly name: string }>()( - Effect.fn("saveUser")(function*(input) { - return yield* UserApi.use((api) => api.save(input)) - }) -) +const saveUser = Atom.fn<{ + readonly id: string + readonly name: string +}>()(Effect.fn("saveUser")(function*(input) { + return yield* UserApi.use((api) => api.save(input)) +})) registry.set(saveUser, { id: "1", name: "Ada" }) ``` -Write `Atom.Reset` to clear an `Atom.fn` result back to its initial state and -`Atom.Interrupt` to interrupt the current computation. Set `{ concurrent: true }` -only when multiple writes should run at the same time; the default interrupts -and replaces the previous run. +Write `Atom.Reset` to restore the initial command state and `Atom.Interrupt` to +interrupt the active computation. Use `{ concurrent: true }` only when writes +may overlap and a single shared result atom is still the correct interface. If +callers need individually correlated results, model the operation as a normal +Effect instead. + +## Keep Identity Stable + +Registry caching normally uses atom identity. Do not create parameterized atoms +inline during reads or React renders. Use `Atom.family` so equal inputs return +the same live atom object. + +```ts +const userAtom = Atom.family((id: string) => + UserRuntime.atom( + UserApi.use((api) => api.getUser(id)) + ).pipe(Atom.withLabel(`user:${id}`)) +) +``` + +`Atom.family` uses Effect hashing/equality and weakly holds produced objects when +the platform supports `WeakRef`. Prefer primitive ids or immutable Effect data +as family keys; mutating an object used as a hashed key breaks lookup +assumptions. + +Serializable atoms are different: their serialization key becomes the registry +key. Two atom objects with the same serializable key alias the same registry +node. Keys must therefore be unique and their schemas compatible throughout one +registry. + +Use `Atom.withLabel` on important atoms for diagnostics. It does not change +runtime behavior. ## Read Dependencies Deliberately -Inside an atom read, `get(atom)` records a dependency. When that dependency -changes, the current atom is invalidated. Use `get.once(atom)` when you need the -current value without creating a dependency edge. +In a normal atom read: -Use `get.result(asyncAtom)` to await an `AsyncResult` atom from another effect -atom. It waits while the result is `Initial`; pass `{ suspendOnWaiting: true }` -when stale values marked `waiting` should also suspend. +- `get(atom)` or `get.get(atom)` records a dependency. +- `get.once(atom)` reads without a dependency edge. +- `get.result(asyncAtom)` records a dependency and suspends the current atom + effect while the result is `Initial`. +- `get.resultOnce(asyncAtom)` waits once without making it a reactive + dependency. + +Pass `{ suspendOnWaiting: true }` when stale values marked `waiting` must also +suspend. ```ts const enrichedUser = Atom.make((get) => Effect.gen(function*() { - const user = yield* get.result(userAtom("1"), { suspendOnWaiting: true }) + const user = yield* get.result(userAtom("1"), { + suspendOnWaiting: true + }) return { ...user, displayName: user.name.toUpperCase() } }) ) ``` -In `Atom.fn` bodies, `get.result` behaves as a one-shot wait instead of a normal -dependency read. If the command should rerun from another atom changing, read -that atom with `get(atom)` as part of the command trigger or use reactivity keys. +`Atom.fn` and `Atom.fnSync` are commands, not reactive derivations. Reads through +their `FnContext` are one-shot and do **not** add dependency edges. A command +reruns only when written again. Use a normal derived atom when work should rerun +because another atom changed, or pass the current value as part of the command +input. -## Handle AsyncResult As State +Use `get.subscribe`, `get.mount`, and `get.addFinalizer` only for explicit +lifecycle integration. Their cleanup is tied to the current atom lifetime. + +## Treat AsyncResult As A State Machine `AsyncResult` has three variants: `Initial`, `Success`, and `Failure`. The `waiting` flag is an overlay, not a fourth variant. A waiting success or failure -can still contain the previous usable value. +can retain useful stale data while a refresh runs. Prefer `AsyncResult.matchWithWaiting`, `AsyncResult.builder`, or explicit -refinements instead of assuming any non-success is a loading state. +refinements instead of equating every non-success with loading. ```ts const view = AsyncResult.matchWithWaiting(result, { @@ -133,33 +190,42 @@ const view = AsyncResult.matchWithWaiting(result, { }) ``` -`AsyncResult.value(result)` and `AsyncResult.getOrElse(result, fallback)` may -return a previous success stored inside a failure. Inspect -`AsyncResult.cause(result)` or `AsyncResult.error(result)` when current failure -versus stale data matters. +`AsyncResult.value(result)` and `AsyncResult.getOrElse(result, fallback)` can +return a previous success stored inside a failure. This is useful for keeping +already loaded pages or stale server data visible. Inspect +`AsyncResult.cause(result)` or `AsyncResult.error(result)` when the current +failure must be shown separately from the fallback value. + +Do not drop the `waiting` flag when mapping UI state. It is the signal that a +displayed success or failure is stale and a new computation is active. -## Manage Lifetime Explicitly +## Make Lifetime A Product Decision -Unobserved atoms are auto-disposed by default. That means local state can reset, -effects can restart, streams can resubscribe, and finalizers can run after the -last listener or dependent child disappears. +Unobserved atoms auto-dispose by default. Disposal can reset local state, +interrupt effects, close stream scopes, remove subscriptions, and run +finalizers. -Use the narrowest lifetime tool that matches the behavior: +Use the narrowest lifetime tool that matches the requirement: -- `Atom.keepAlive` keeps an atom cached even when unobserved. -- `Atom.setIdleTTL(duration)` keeps an unused atom around for a finite idle time. -- `Atom.autoDispose` restores default disposal on a copied atom. -- `registry.mount(atom)` keeps an atom alive until the returned release function - is called. -- `Atom.mount(atom)` keeps an atom alive for the current Effect scope. +- `registry.subscribe(atom, listener)` observes until its release callback runs. +- `registry.mount(atom)` keeps it alive until its release callback runs. +- `Atom.mount(atom)` keeps it alive for the current Effect scope. +- `Atom.setIdleTTL(duration)` retains it for a finite unobserved interval. +- `Atom.keepAlive` retains it for the entire registry lifetime. +- `Atom.autoDispose` removes a copied atom's `keepAlive` behavior. -Always release `registry.subscribe` and `registry.mount` callbacks when -integrating with external callback-based code. +Always release registry subscriptions and mounts. Be especially careful with a +high-cardinality `Atom.family`: combining it with `keepAlive` or a long TTL can +turn a weak family into an effectively unbounded registry cache. -## Batch Related Writes +Registry `initialValues` seed a node but do not mount it. The seeded value is +still subject to normal disposal and recomputation. -Use `Atom.batch` when multiple synchronous writes should invalidate dependents -and notify listeners once after the final state is known. +## Batch Related Synchronous Writes + +Use `Atom.batch` when several synchronous writes form one logical state change. +Dependents can rebuild from the latest values inside the batch, but listeners +are notified after the outermost batch commits. ```ts Atom.batch(() => { @@ -168,23 +234,21 @@ Atom.batch(() => { }) ``` -Reads inside a batch can still rebuild from the latest written state, but -listeners are notified after the batch commits. +`Atom.batch` is not an Effect transaction and does not wait for async work. Use +it only around synchronous registry operations. ## Use Runtime Atoms For Services -Use `Atom.runtime(layer)` or `Atom.context({ memoMap })` when atom effects need -Effect services. The runtime builds the layer with a memo map, provides -`AtomRegistry`, `Scope`, `Scheduler`, and `Reactivity`, and exposes -`runtime.atom`, `runtime.fn`, `runtime.pull`, and `runtime.subscriptionRef`. +Plain async atoms may require only `Scope` and `AtomRegistry`. Use an atom +runtime when effects also require application services. ```ts const UserRuntime = Atom.runtime(UserApi.layer) const user = Atom.family((id: string) => - UserRuntime.atom(UserApi.use((api) => api.getUser(id)), { - initialValue: { id, name: "Loading" } - }) + UserRuntime.atom( + UserApi.use((api) => api.getUser(id)) + ) ) const saveUser = UserRuntime.fn( @@ -195,49 +259,114 @@ const saveUser = UserRuntime.fn( ) ``` -Use registry `initialValues` with `Atom.initialValue(runtime.layer, testLayer)` -to replace runtime services in tests. +`Atom.runtime(layer)` uses the default shared `Layer.MemoMap`. +`Atom.context({ memoMap })` creates a factory with an explicit memoization +boundary and exposes `addGlobalLayer`. The runtime supplies `AtomRegistry`, +`Scope`, `Scheduler`, and `Reactivity` while building its layer. -## Invalidate Server State With Reactivity Keys +In tests, replace a runtime layer with registry initial values: -Use `Atom.withReactivity(keys)` for reads that should refresh after matching -invalidations. Use `runtime.fn(..., { reactivityKeys })`, -`Reactivity.mutation(effect, keys)`, or `Reactivity.invalidate(keys)` for writes -that should trigger those refreshes after success. +```ts +const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue(UserRuntime.layer, UserApi.testLayer) + ] +}) +``` + +Mount the resource under test so the seeded runtime and async work are not +disposed between assertions. + +## Model Server Resources With SWR And TTL -Keys can be a flat array or a record. Prefer stable primitive keys or stable ids; -non-primitive keys are matched by `Hash.hash`. +`Atom.swr` adds stale-while-revalidate behavior to an `AsyncResult` atom. +`staleTime` controls freshness; `revalidateOnMount` and `revalidateOnFocus` +control automatic refresh triggers. ```ts -const user = UserRuntime.atom(UserApi.use((api) => api.getUser("1"))).pipe( - UserRuntime.factory.withReactivity({ users: ["1"] }) +const usersResource = UserRuntime.atom( + UserApi.use((api) => api.listUsers()) +).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnMount: true, + revalidateOnFocus: true, + focusSignal: appFocusSignal + }), + Atom.setIdleTTL("5 minutes") ) +``` -const saveUser = UserRuntime.fn( - (input: User) => UserApi.use((api) => api.saveUser(input)), - { reactivityKeys: { users: ["1"] } } +Important details: + +- A manual registry refresh is forceful even while data is fresh. +- A stale success or failure keeps its previous success while revalidation + runs. +- Focus revalidation requires both `revalidateOnFocus` and a `focusSignal`. +- `true` respects `staleTime`; `"always"` forces a refresh on every signal. +- Freshness uses `AsyncResult` timestamps and host `Date.now`. + +Apply resource policy combinators before `Atom.serializable`. Transforming an +atom can intentionally remove its serializable marker; serialize the final +public resource that should cross registries. + +## Invalidate Server State With Reactivity Keys + +Attach keys to reads with `Atom.withReactivity` or +`runtime.factory.withReactivity`. Invalidate them through +`runtime.fn(..., { reactivityKeys })`, `Reactivity.mutation`, or +`Reactivity.invalidate`. + +```ts +const user = UserRuntime.atom( + UserApi.use((api) => api.getUser("1")) +).pipe( + UserRuntime.factory.withReactivity({ users: ["1"] }) ) ``` +Key matching is hash based, not deep structural matching. Prefer primitives or +immutable Effect data. Record keys have hierarchical behavior: +`{ users: ["1"] }` registers both `"users"` and `"users:1"`. Consequently, +two records with the same `users` group overlap even when their ids differ. Use +a flat composite key such as `["users:1"]` when invalidation must be strictly +per id. + +`Reactivity.mutation(effect, keys)` invalidates only after an Effect succeeds. +A stream-returning runtime command uses stream finalization to invalidate, so it +also invalidates when that stream ends through failure or interruption. Choose +the command shape with that distinction in mind. + ## Streams, Pulls, And SubscriptionRefs -An `Atom.make(stream)` stores the latest emitted item in an `AsyncResult`. An -empty stream completes as `NoSuchElementError`. Failures preserve the latest -previous success when possible. +`Atom.make(stream)` subscribes while the atom is alive and stores the latest +emitted item as an `AsyncResult`. A stream that completes without emitting fails +with `NoSuchElementError`. A later failure preserves a previous success when one +exists. -Use `Atom.pull(stream)` or `runtime.pull(stream)` for paginated or incremental -streams that should advance only when the atom is written to. It accumulates -items by default; pass `{ disableAccumulation: true }` when each pull should -replace the previous batch. +Use `Atom.pull` for demand-driven pagination. The first chunk is pulled when the +atom is read; each write requests another chunk. + +```ts +const pages = Atom.pull( + Stream.paginate(initialCursor, fetchPage) +) +``` + +With default accumulation, the success value contains `{ items, done }` and +`done` becomes true after the terminal pull. Pass +`{ disableAccumulation: true }` when each pull should replace the prior batch or +when retaining the entire history would be too expensive. In that mode, a +terminal pull with no new items can surface `NoSuchElementError` instead of a +final accumulated `{ done: true }` value. Avoid issuing another write while the +pull result is already waiting unless overlapping pulls are intentional. Use `Atom.subscriptionRef(refOrEffect)` when state already lives in a -`SubscriptionRef`; writes to the atom update the ref. +`SubscriptionRef`; atom writes update the ref. ## Persistence And Hydration -Mark atoms that cross registry boundaries with `Atom.serializable({ key, -schema })`. Only serializable atoms are included in `Hydration.dehydrate`, and -`Hydration.hydrate` must run before the matching atoms are first read. +Mark only state that crosses registry boundaries as serializable. ```ts const user = userAtom("1").pipe( @@ -254,53 +383,66 @@ const state = Hydration.dehydrate(serverRegistry) Hydration.hydrate(clientRegistry, state) ``` -Stable keys matter more than atom identity during hydration. The target atom must -use a compatible schema. `Hydration.dehydrate(..., { encodeInitialAs: "promise" })` -uses a live JavaScript promise for initial async results, so do not serialize -that form across JSON or processes. - -For browser URL state, `Atom.searchParam` requires synchronous schemas with no -Effect context. For storage-backed state, use `Atom.kvs` with an atom runtime -that provides `KeyValueStore`. - -## Remote API Helpers - -Use `AtomHttpApi.Service` or `AtomRpc.Service` when typed HTTP API or RPC -clients should participate in atom caching, invalidation, and hydration. - -- Queries return `Atom>` for non-streaming endpoints. -- Mutations return `AtomResultFn`. -- `reactivityKeys` connect successful mutations to query refreshes. -- `timeToLive` maps to `Atom.setIdleTTL` for finite durations and - `Atom.keepAlive` for infinite durations. -- `serializationKey` is required for serializable queries, and should uniquely - identify the endpoint plus request. -- RPC streaming queries return pull atoms and are not serializable query atoms. +Hydrate before the target atom is first read. Hydration preloads encoded values +by key; it does not replace an already-built node. Serialization uses +synchronous JSON codecs, so schemas must not require Effect services. -## Testing Patterns +`dehydrate` ignores `AsyncResult.Initial` by default. The other modes are: -Use `it.effect` for Effect-based tests, `AtomRegistry.make()` for an isolated -cache, fake timers or `TestClock` for delayed atoms, and explicit -`registry.mount(atom)` when async work must stay alive during the test. +- `"value-only"` encodes the initial value itself. +- `"promise"` includes a live promise that later updates the target registry. -```ts -it.effect("refreshes after mutation", () => - Effect.gen(function*() { - const registry = AtomRegistry.make() - const unmount = registry.mount(user) +The promise mode is process-local JavaScript state. Do not JSON-serialize it or +send it across a network/process boundary. - registry.set(saveUser, { id: "1", name: "Grace" }) - yield* Effect.yieldNow +For browser URL state, `Atom.searchParam` requires a synchronous schema with no +context. For storage state, use `Atom.kvs` with a runtime providing +`KeyValueStore`. - const result = registry.get(user) - assert(AsyncResult.isSuccess(result)) - assert.strictEqual(result.value.name, "Grace") +## Remote API Helpers - unmount() - })) -``` +Use `AtomHttpApi.Service` or `AtomRpc.Service` when a typed HTTP API or RPC +client should participate directly in atom caching and invalidation. -Prefer `yield* Effect.yieldNow`, fake timer advancement, or `TestClock` over -real sleeps. Assert `AsyncResult` variants and `waiting` flags directly. For -lifetime behavior, assert node disposal by reading again after yielding, or use -`keepAlive` / `mount` when state should persist. +- Non-streaming queries return `Atom>`. +- Mutations return `AtomResultFn`. +- `reactivityKeys` connect commands to query refreshes. +- `timeToLive` maps finite values to idle TTL and infinity to `keepAlive`. +- Serializable queries require a unique `serializationKey` per endpoint and + request. +- Streaming RPC queries return pull atoms and are not serializable query atoms. + +Prefer the project's generated client plus hand-written transport/service layer +when those helpers would duplicate existing abstractions. + +## Testing + +Create a fresh registry per test and dispose it or release every mount. Test the +state machine, not only the final success: + +- initial and waiting state +- stale success during refresh +- failure with and without previous success +- interruption or latest-write-wins behavior +- finalizers and disposal +- pagination completion and accumulated items + +Use regular Vitest tests for purely imperative registry logic. Run effectful +assertions with the repository's existing `Effect.runPromise` pattern. Mount +async atoms before yielding or advancing time. + +Registry idle TTL, `Atom.swr`, `Atom.debounce`, and several focus/storage helpers +use host timers or `Date.now`; control those with the test runner's fake timers. +Use `TestClock` only when the Effect inside the runtime is explicitly receiving +the test clock service. + +## Review Checklist + +- Atom and serializable identities are stable and unique. +- Command reads are not mistaken for reactive dependencies. +- Async UI preserves both stale values and the `waiting` flag. +- Every subscription or mount has a matching release. +- High-cardinality families have a bounded cache policy. +- Reactivity keys have the intended broad or per-id overlap. +- Hydration happens before first read and uses a synchronous compatible schema. +- Time and finalization behavior are covered by tests. diff --git a/agent-patterns/effect-http-client.md b/agent-patterns/effect-http-client.md index 7d6ad9168..50b3fa2dc 100644 --- a/agent-patterns/effect-http-client.md +++ b/agent-patterns/effect-http-client.md @@ -1,31 +1,54 @@ # Effect HttpClient Patterns -Use this when writing project code that talks to external HTTP APIs with Effect. -The source of truth reviewed for these patterns is the vendored Effect repo: -`repos/effect/LLMS.md`, `repos/effect/ai-docs/src/50_http-client/10_basics.ts`, -and the `repos/effect/packages/effect/src/unstable/http/HttpClient*.ts` modules -plus their tests. +Use this guide for outgoing HTTP APIs: request construction, generated clients, +middleware, retries, rate limits, response decoding, streaming bodies, and +tests. -## Imports +The source of truth is the vendored Effect repository, especially: -Prefer the unstable HTTP barrel unless nearby code imports individual modules. +- `@repos/effect/packages/effect/src/unstable/http/HttpClient.ts` +- `@repos/effect/packages/effect/src/unstable/http/HttpClientRequest.ts` +- `@repos/effect/packages/effect/src/unstable/http/HttpClientResponse.ts` +- `@repos/effect/packages/effect/src/unstable/http/HttpClientError.ts` +- `@repos/effect/packages/effect/src/unstable/http/HttpIncomingMessage.ts` +- `@repos/effect/packages/effect/src/unstable/http/FetchHttpClient.ts` +- `@repos/effect/packages/effect/src/unstable/persistence/RateLimiter.ts` +- `@repos/effect/packages/effect/test/unstable/http` + +Read `effect-stream.md` as well when a request or response body is streamed. + +## Imports And Transport + +Match nearby code. The unstable HTTP barrel is convenient for hand-written +services; generated clients in this repository use module imports. ```ts import { Context, Effect, flow, Layer, Schedule, Schema, Stream } from "effect" -import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" +import { + FetchHttpClient, + HttpClient, + HttpClientRequest, + HttpClientResponse +} from "effect/unstable/http" import { RateLimiter } from "effect/unstable/persistence" ``` -Platform implementations are provided as layers. Use `FetchHttpClient.layer` for -portable fetch-based code unless the runtime has a more specific layer nearby -(`NodeHttpClient.layerFetch`, `NodeHttpClient.layerNodeHttp`, -`NodeHttpClient.layerUndici`, `BrowserHttpClient.layerXMLHttpRequest`, etc.). +An `HttpClient` describes request preprocessing and response postprocessing. A +platform layer supplies the low-level transport: + +- `FetchHttpClient.layer` for browsers, edge runtimes, and portable Fetch code +- a nearby Node or browser-specific client when its transport behavior is + required -## Build API Clients As Services +Fetch behavior such as CORS, credentials, redirect defaults, and streaming +support still depends on the runtime. Do not assume all platform layers behave +identically. -Wrap API-specific HTTP behavior in a `Context.Service` layer. Acquire -`HttpClient.HttpClient` once, then apply shared middleware such as base URL, -headers, status filtering, retries, tracing spans, and error mapping. +## Put API Policy In A Service Boundary + +Generated code should describe endpoints. Hand-written transport or service +layers should own base URLs, authentication, retry policy, observability, and +domain error mapping. ```ts class Todo extends Schema.Class("Todo")({ @@ -57,27 +80,29 @@ export class TodoApi extends Context.Service new TodoApiError({ cause })), - Effect.withSpan("TodoApi.getTodo") + Effect.mapError((cause) => new TodoApiError({ cause })) ) }) return TodoApi.of({ getTodo }) }) - ).pipe( - Layer.provide(FetchHttpClient.layer) - ) + ).pipe(Layer.provide(FetchHttpClient.layer)) } -export class TodoApiError extends Schema.TaggedErrorClass()("TodoApiError", { - cause: Schema.Defect -}) {} +export class TodoApiError extends Schema.TaggedErrorClass()( + "TodoApiError", + { cause: Schema.Defect() } +) {} ``` -## Requests Are Immutable Values +Acquire and configure the shared client once while building the service. Keep +endpoint functions small and map library errors into domain errors at this +boundary. Do not leak authentication headers or raw response bodies into logs. + +## Build Immutable Requests Use `HttpClientRequest` constructors and combinators instead of hand-building -fetch options. Each combinator returns a new request. +Fetch options. Every combinator returns a new request. ```ts const request = HttpClientRequest.post("/todos").pipe( @@ -90,9 +115,13 @@ const request = HttpClientRequest.post("/todos").pipe( const response = yield* client.execute(request) ``` -Prefer schema-backed encoders when a schema exists. `schemaBodyJson` and -`bodyJson` fail in the Effect error channel; `bodyJsonUnsafe` may throw during -JSON encoding and is best reserved for generated code or already-safe payloads. +Choose body encoding deliberately: + +- `schemaBodyJson(schema)(value)` validates/encodes through a schema and can + require encoding services. +- `bodyJson(value)` catches JSON encoding failures as `HttpBodyError`. +- `bodyJsonUnsafe(value)` is synchronous but may throw. Reserve it for generated + code or values whose serializability is already guaranteed. ```ts const createTodo = (input: typeof NewTodo.Type) => @@ -103,47 +132,92 @@ const createTodo = (input: typeof NewTodo.Type) => ) ``` -Use `setUrlParams` when replacing query values and `appendUrlParams` when -multiple values with the same key must be preserved. Passing a `URL` to a -request constructor extracts search parameters and hash into structured request -fields. +Use `setUrlParams` to replace values and `appendUrlParams` to preserve repeated +keys. Passing a `URL` to a request constructor extracts its query and hash into +the request's structured fields. + +Be careful with middleware that sets headers: later `setHeader` calls replace +the same header. Prefer one clearly owned authentication transform. -## Status Codes Are Not Errors By Default +## Decide Status Policy Before Decoding -`HttpClient` succeeds with an `HttpClientResponse` for non-2xx statuses. Choose -one of these explicitly: +HTTP status is data by default. `HttpClient` succeeds with a response for 4xx +and 5xx statuses unless a filter turns them into failures. + +Use a client-wide filter only when every endpoint has the same status contract: ```ts -// Simple API: fail on anything outside 2xx. -const json = yield* client.get("/todos/1").pipe( - Effect.flatMap(HttpClientResponse.filterStatusOk), +const okClient = client.pipe(HttpClient.filterStatusOk) + +const todo = yield* okClient.get("/todos/1").pipe( Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)) ) ``` +For typed non-2xx responses, do not apply `filterStatusOk` before matching. +Branch on the raw response first: + ```ts -// Typed API: branch by exact status or status class. const result = yield* client.post("/todos").pipe( Effect.flatMap(HttpClientResponse.matchStatus({ 201: HttpClientResponse.schemaBodyJson(Todo), 400: (response) => - Effect.flatMap( - HttpClientResponse.schemaBodyJson(ApiProblem)(response), - (problem) => Effect.fail(new BadRequest({ problem })) + HttpClientResponse.schemaBodyJson(ApiProblem)(response).pipe( + Effect.flatMap((problem) => Effect.fail(new BadRequest({ problem }))) ), - "5xx": (response) => Effect.fail(new RemoteServiceUnavailable({ status: response.status })), - orElse: (response) => Effect.fail(new UnexpectedStatus({ status: response.status })) + "5xx": (response) => + Effect.fail(new RemoteServiceUnavailable({ status: response.status })), + orElse: (response) => + Effect.fail(new UnexpectedStatus({ status: response.status })) })) ) ``` -Use `HttpClient.filterStatusOk` on the client when every request made by that -client expects 2xx responses. Use `HttpClientResponse.matchStatus` when the API -has typed non-2xx responses. +Exact status handlers win over status-class handlers. Always include `orElse` +so new or undocumented statuses have an explicit path. + +## Know The Error Layers + +There is not one universal HTTP error type. The operation determines the error +channel: -## Decode Responses Deliberately +| Operation | Typical failure | +| --- | --- | +| Request schema/JSON encoding before execution | `HttpBodyError` | +| Transport, URL construction, status filtering, body reading | `HttpClientError` | +| Schema validation of decoded body/headers | `SchemaError` | +| Hand-written API boundary | mapped domain error | -Body readers are effects and can fail with `HttpClientError`: +`HttpClientError` wraps a more specific `reason`: + +- `TransportError`, `EncodeError`, and `InvalidUrlError` have no response. +- `StatusCodeError`, `DecodeError`, and `EmptyBodyError` include a response. + +Catch the outer `HttpClientError`, then inspect `reason._tag`. Catching +`StatusCodeError` directly with `Effect.catchTag` does not work on a normal +client call because it is nested. + +```ts +const recovered = client.get("/todos/1").pipe( + Effect.flatMap(HttpClientResponse.filterStatusOk), + Effect.catchTag("HttpClientError", (error) => { + if ( + error.reason._tag === "StatusCodeError" && + error.response?.status === 404 + ) { + return Effect.succeedNone + } + return Effect.fail(error) + }) +) +``` + +This handler does not catch `SchemaError` from a later schema decoder. Map both +at the service boundary when the public API exposes one domain error. + +## Decode A Body Once, Deliberately + +Raw body readers are effects and can fail with `HttpClientError`: ```ts yield* response.text @@ -152,7 +226,7 @@ yield* response.arrayBuffer yield* response.urlParamsBody ``` -Prefer schema decoders for JSON and headers: +Prefer schema decoders at trust boundaries: ```ts yield* HttpClientResponse.schemaBodyJson(Todo)(response) @@ -160,72 +234,84 @@ yield* HttpClientResponse.schemaJson(ResponseEnvelope)(response) yield* HttpClientResponse.schemaNoBody(HeaderOnlyResponse)(response) ``` -Use `schemaBodyJson` for body-only JSON. Use `schemaJson` when the schema covers -the whole `{ status, headers, body }` response shape. `response.json` parses an -empty text body as `null`. `response.stream` fails if there is no body. +- `schemaBodyJson` decodes only the JSON body. +- `schemaJson` decodes `{ status, headers, body }` together. +- `schemaNoBody` decodes status and headers without consuming a body. +- `response.json` treats an empty text body as `null`. +- `response.stream` fails with `EmptyBodyError` when there is no body. -## Error Handling +Text and array-buffer access are cached by the Web response wrapper, but a body +stream is a live consumable resource. Do not inspect a body through `text` or +`json` and then expect to stream the same body, or stream it and then decode it. +Choose buffered decoding or streaming at the endpoint boundary. -The public failure is usually `HttpClientError.HttpClientError`. Inspect -`error.reason._tag` for the precise cause: +## Understand Middleware Ordering -- `TransportError`, `EncodeError`, and `InvalidUrlError` happen before a - response exists. -- `StatusCodeError`, `DecodeError`, and `EmptyBodyError` include the response. +Request transforms and response transforms compose differently: -Do not try to catch `StatusCodeError` directly with `Effect.catchTag` on a normal -client call; the outer tag is `HttpClientError`. Catch `HttpClientError` and -branch on `reason._tag`, or map errors at the API service boundary. +- `mapRequest` appends preprocessing; successive calls execute in pipe order. +- `mapRequestInput` prepends preprocessing before transforms already installed. +- Each later response combinator wraps the response behavior built before it. ```ts -const recovered = client.get("/todos/1").pipe( - Effect.flatMap(HttpClientResponse.filterStatusOk), - Effect.catchTag("HttpClientError", (error) => { - if (error.reason._tag === "StatusCodeError" && error.response?.status === 404) { - return Effect.succeedNone - } - return Effect.fail(error) - }) +const client = baseClient.pipe( + HttpClient.mapRequest( + HttpClientRequest.prependUrl("https://api.example.com") + ), + HttpClient.followRedirects(), + HttpClient.filterStatusOk, + HttpClient.retryTransient({ times: 3 }) ) ``` -## Middleware Ordering +Ordering consequences: + +- Put `followRedirects` before `filterStatusOk`; otherwise a visible 3xx can be + rejected before redirect handling sees it. +- Put `tap` before a status filter to inspect every response, including non-2xx. + Put it after the filter to observe accepted responses only; use `tapError` for + rejected ones. +- Middleware inside a retry wrapper runs for every attempt. Middleware added + after the retry sees only the final outcome. +- `filterStatusOk` may appear before `retryTransient`: transient + `StatusCodeError` reasons are recognized. Without a filter, transient raw + responses are also recognized by the default response retry mode. -`HttpClient` middleware transforms a client and is usually read left to right in -the `pipe`. +Use `mapRequestInput` rarely. Most base URLs, headers, and auth belong in normal +`mapRequest` transforms whose order is visible in the pipe. + +## Retry Only Safe Operations + +`retryTransient` retries timeouts, transport errors, and these statuses: +`408`, `429`, `500`, `502`, `503`, and `504`. Its default mode is +`"errors-and-responses"`. ```ts -const client = baseClient.pipe( - HttpClient.mapRequest(HttpClientRequest.prependUrl("https://api.example.com")), - HttpClient.mapRequest(HttpClientRequest.bearerToken(token)), - HttpClient.filterStatusOk, - HttpClient.retryTransient({ times: 3 }) +const retried = client.pipe( + HttpClient.retryTransient({ + schedule: Schedule.exponential("100 millis"), + times: 3 + }) ) ``` -Use `mapRequest` for transformations that should run after existing request -preprocessing. Use `mapRequestInput` only when a transformation must run before -previously installed request middleware. - -Useful middleware: - -- `mapRequest` / `mapRequestEffect` for base URLs, headers, auth, and request - normalization. -- `filterStatus` / `filterStatusOk` for turning unacceptable statuses into - failures. -- `retryTransient` for transport failures, timeouts, and transient statuses - (`408`, `429`, `500`, `502`, `503`, `504`). The default `retryOn` is - `"errors-and-responses"`. -- `followRedirects()` for following 3xx `location` headers, defaulting to 10 - redirects. -- `withCookiesRef` for cookie jars across requests. -- `tap`, `tapError`, and `tapRequest` for logging/metrics without changing the - response. +`times` counts retries, not total attempts. `times: 3` can execute four total +attempts. A custom `while` predicate adds errors to the built-in transient set; +it does not replace that set, and it is ignored in `"response-only"` mode. + +The client does **not** check whether the HTTP method is idempotent. The same +policy retries GET and POST. Before applying retries to mutations, require an +API-supported idempotency key or other proof that replay cannot duplicate a +side effect. Prefer separate read and mutation clients when their retry policies +differ. + +Also confirm the request body can be replayed. A live stream or external handle +may not be safe to execute again even when the method is idempotent. ## Rate Limiting -Use `HttpClient.withRateLimiter` with the `RateLimiter` service when requests -must share a limit by key. +`HttpClient.withRateLimiter` coordinates requests that share a key through the +`RateLimiter` service. ```ts const limited = client.pipe( @@ -238,73 +324,116 @@ const limited = client.pipe( ) ``` -By default, it inspects common rate-limit headers such as `ratelimit-limit`, -`x-ratelimit-limit`, `ratelimit-remaining`, `retry-after`, and reset headers. -It also retries `429` responses, including `HttpClientError` values wrapping a -429 `StatusCodeError`, by sending the retry through the limiter again. Set -`disableResponseInspection: true` only when response headers should not affect -future limits. +Pick keys with intentional cardinality: per upstream, account, or endpoint. +Avoid accidentally including unique query data when all requests should share a +limit. + +By default the middleware learns from common `RateLimit-*`, `X-RateLimit-*`, and +`Retry-After` headers. It sends 429 responses, including filtered 429 +`HttpClientError` values, back through the limiter. This 429 loop is not bounded +by a `times` option, so use an enclosing timeout/cancellation policy when the +upstream may remain rate-limited indefinitely. + +`disableResponseInspection: true` stops header-based learning. It does not turn +off 429 retries; those still pass through the configured limiter. + +Provide both the limiter and its store layer at the application/test boundary. +Use `TestClock` in rate-limit tests rather than real waits. -## Streaming And Scope +## Stream Bodies And Control Scope -For response bodies, prefer `HttpClientResponse.stream(effect)` or unwrap -`response.stream` and consume it with `Stream` combinators. +Convert a response effect directly into a byte stream: ```ts const text = yield* client.get("/events").pipe( - Effect.map((response) => response.stream), - Stream.unwrap, + HttpClientResponse.stream, Stream.decodeText(), Stream.mkString ) ``` -Non-scoped responses are tied to an abort controller so interrupted body reads -and early-ending streams abort the underlying request. If a request lifetime -must be controlled by a surrounding `Scope`, apply `HttpClient.withScope`. +Keep the body incremental for large or open-ended responses. Do not use +`Stream.mkString`, `runCollect`, or `mkUint8Array` unless the body is known to be +finite and bounded. -## Tracing +Normal responses use an abort controller. Interrupting a body read or ending a +response stream early aborts the underlying request. Apply +`HttpClient.withScope` when the request lifetime must instead be attached to an +explicit surrounding `Scope`; closing that scope aborts it. + +## Tracing And Inspection HttpClient creates client spans by default and records method, URL, status, and -redacted headers. Use `Effect.withSpan` around domain operations and -`Effect.annotateCurrentSpan` for request-specific attributes. +redacted headers. Wrap domain operations in named `Effect.fn` functions or +`Effect.withSpan`, and add safe request identifiers with +`Effect.annotateCurrentSpan`. -Context references can tune tracing: +Context references can tune client tracing: - `HttpClient.TracerDisabledWhen` disables spans for matching requests. - `HttpClient.TracerPropagationEnabled` controls outgoing trace headers. -- `HttpClient.SpanNameGenerator` customizes span names. +- `HttpClient.SpanNameGenerator` customizes client span names. -## Testing Patterns +Use `tap`, `tapError`, and `tapRequest` for logging or metrics without changing +results. Body inspection is a real decode and can consume a streamed body; do +it only for endpoints that use buffered bodies. Keep secrets in headers covered +by Effect's redaction configuration. -Use `it.effect`, `assert` / `strictEqual` helpers from `@effect/vitest`, and -`TestClock` for time-dependent behavior. Avoid `Effect.runSync` in tests. +## Testing -Test API code with `HttpClient.make` and `HttpClientResponse.fromWeb` rather -than real network calls. +Use a fake `HttpClient.make` transport instead of the network. Build responses +with `HttpClientResponse.fromWeb` and assert the fully preprocessed request when +middleware behavior matters. ```ts -it.effect("decodes todo", () => - Effect.gen(function*() { - const client = HttpClient.make((request) => - Effect.succeed( - HttpClientResponse.fromWeb( - request, - new Response(JSON.stringify({ id: 1, title: "Test", completed: false }), { +import { expect, it } from "vitest" + +it("decodes a todo", async () => { + const client = HttpClient.make((request) => + Effect.succeed( + HttpClientResponse.fromWeb( + request, + new Response( + JSON.stringify({ + id: 1, + title: "Test", + completed: false + }), + { status: 200, headers: { "content-type": "application/json" } - }) + } ) ) ) + ) - const todo = yield* program.pipe( + const todo = await Effect.runPromise( + HttpClient.get("https://example.com/todos/1").pipe( + Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)), Effect.provide(Layer.succeed(HttpClient.HttpClient, client)) ) + ) - strictEqual(todo.id, 1) - })) + expect(todo.id).toBe(1) +}) ``` -For retry and rate-limit tests, count attempts in a `Ref`, fork the request, and -advance `TestClock` instead of waiting in real time. +Cover at least the status and decode cases in the public contract. For retry, +redirect, timeout, and rate-limit behavior: + +- count attempts with `Ref` +- fork with `startImmediately: true` when the operation must reach a sleep +- provide `TestClock.layer()` and advance `TestClock` from `effect/testing` +- assert interruption and abort signals when lifetime is part of correctness + +## Review Checklist + +- Generated clients remain generated; policy lives in hand-written layers. +- Every endpoint explicitly filters or matches status. +- Request encoding, `HttpClientError`, and `SchemaError` are mapped intentionally. +- Middleware order matches which attempts/responses each observer should see. +- Retried methods and bodies are safe to replay. +- Rate-limit keys are bounded and persistent 429 behavior is cancellable. +- Large or infinite bodies remain streamed and are consumed once. +- Tests cover non-2xx, invalid payloads, timing, and cancellation as applicable. diff --git a/agent-patterns/effect-stream.md b/agent-patterns/effect-stream.md index a32eaef12..32bead04c 100644 --- a/agent-patterns/effect-stream.md +++ b/agent-patterns/effect-stream.md @@ -1,14 +1,24 @@ # Effect Stream Patterns -Use this when writing project code that models incremental, pull-based data with -Effect `Stream`. The source of truth reviewed for these patterns is the vendored -Effect repo: `@repos/effect/LLMS.md`, -`@repos/effect/ai-docs/src/02_stream/*`, and -`@repos/effect/packages/effect/src/Stream.ts`. +Use this guide when data is incremental, pull-based, potentially large, or +open-ended: polling, pagination, events, queues, byte streams, concurrent +pipelines, and resource-backed producers. + +The source of truth is the vendored Effect repository, especially: + +- `@repos/effect/packages/effect/src/Stream.ts` +- `@repos/effect/ai-docs/src/02_stream` +- `@repos/effect/packages/effect/test/Stream.test.ts` +- platform adapters such as + `@repos/effect/packages/platform-node-shared/src/NodeStream.ts` + +Read `effect-http-client.md` as well for HTTP response or request bodies, and +`effect-atoms.md` when a stream backs an atom. ## Imports -Prefer the stable `effect` barrel unless nearby code imports individual modules. +Prefer the stable `effect` barrel unless nearby code uses module imports. +Testing utilities have a separate entry point. ```ts import { @@ -19,52 +29,54 @@ import { Queue, Schedule, Sink, - Stream, - TestClock + Stream } from "effect" +import { TestClock } from "effect/testing" ``` -For Node.js readable streams, use the platform adapter in Node-only code. +For Node.js readable streams, use the platform adapter only in Node-specific +code. ```ts import { NodeStream } from "@effect/platform-node" ``` -## Streams Are Lazy Descriptions +## Mental Model -A `Stream.Stream` describes a sequence that can emit zero or more `A` -values, fail with `E`, and require services from `R`. It does not run until it -is consumed with `Stream.run*`, `Stream.run`, `Stream.toQueue`, -`Stream.toReadableStream*`, or a similar destructor. +`Stream.Stream` is a lazy description that can emit zero or more `A` +values, fail with `E`, and require services `R`. It does not start until a +destructor consumes it. ```ts -const stream = Stream.fromEffect(loadConfig) - -const program = stream.pipe( - Stream.map((config) => config.region), - Stream.runCollect +const regions = Stream.fromEffect(loadConfig).pipe( + Stream.map((config) => config.region) ) -``` - -Reusing the same stream value reruns the description for each consumer. If -several consumers must observe one running producer, use `Stream.share`, -`Stream.broadcast`, `Stream.broadcastN`, a `Queue`, or a `PubSub`. -## Choose The Smallest Constructor - -Use the constructor that matches the source shape. +const program = regions.pipe(Stream.runCollect) +``` -- `Stream.empty`, `Stream.succeed`, `Stream.make`, `Stream.fromIterable` for - in-memory values. -- `Stream.fromEffect` for one effectful value. -- `Stream.fromEffectSchedule` for polling an effect over a schedule. -- `Stream.paginate` for cursor or page APIs. -- `Stream.fromAsyncIterable` for existing async iterables. -- `Stream.fromEventListener` for DOM-style event targets. -- `Stream.callback` for callback APIs that need explicit queue control. -- `Stream.fromQueue` and `Stream.fromPubSub` for Effect concurrency primitives. -- `Stream.fromReadableStream` or `NodeStream.fromReadable` for web or Node - readable streams. +Running `program` twice runs the stream description twice. Reusing a stream +value does not memoize or share a producer. Sharing requires an explicit scoped +combinator such as `share`, `broadcast`, or `broadcastN`. + +## Choose The Smallest Source + +| Source shape | Constructor | +| --- | --- | +| In-memory values | `empty`, `succeed`, `make`, `fromIterable` | +| One effectful value | `fromEffect` | +| Effect repeated on a schedule | `fromEffectSchedule` | +| Cursor/page API | `paginate` | +| Existing async iterable | `fromAsyncIterable` | +| DOM/EventTarget events | `fromEventListener` | +| General callback API | `callback` | +| Effect queue or pubsub | `fromQueue`, `fromPubSub` | +| Web readable stream | `fromReadableStream` | +| Node readable stream | `NodeStream.fromReadable` | + +Prefer a structure-preserving constructor over mutable state inside +`Stream.callback`. Pagination should usually be `Stream.paginate`; polling +should usually be `Stream.fromEffectSchedule`. ```ts const jobs = Stream.paginate(0, (page) => @@ -77,42 +89,45 @@ const jobs = Stream.paginate(0, (page) => ) ``` -Prefer `Stream.paginate` over building a mutable loop with `Stream.callback` for -normal paginated APIs. Prefer `Stream.fromEffectSchedule` over hand-written -sleep loops for polling. +`paginate` emits each item from the iterable returned in the first tuple slot +and continues only while the second slot is `Option.some(nextState)`. Derive the +next cursor from validated source metadata, not from the number of domain items +left after filtering or tolerant decoding. -## Consume With Intent +`fromEffectSchedule` runs the effect immediately, then follows the schedule. +Bound it with `take` when the resulting stream must be finite. -Every stream pipeline should end in a clear consumer. +## End Every Pipeline With An Intentional Consumer -```ts -const writeEvents = events.pipe( - Stream.runForEach((event) => EventStore.use((store) => store.write(event))) -) -``` +Common destructors: -Use `Stream.runCollect` only for finite streams with bounded output. For large -or infinite streams, prefer `Stream.runForEach`, `Stream.runFold`, -`Stream.runFoldEffect`, `Stream.runDrain`, or `Stream.run(Sink...)`. +| Need | Destructor | +| --- | --- | +| Execute an effect per element | `runForEach` | +| Ignore values but run effects | `runDrain` | +| Fold incrementally | `runFold`, `runFoldEffect`, `run(Sink...)` | +| Read an optional edge | `runHead`, `runLast` | +| Collect a bounded stream | `runCollect` | +| Count without retaining values | `runCount` | +| Concatenate bounded text/bytes | `mkString`, `mkUint8Array` | ```ts -const firstTen = source.pipe( - Stream.take(10), - Stream.runCollect -) - -const total = source.pipe( - Stream.map((event) => event.value), - Stream.run(Sink.sum) +const writeEvents = events.pipe( + Stream.runForEach((event) => + EventStore.use((store) => store.write(event)) + ) ) ``` -`Stream.runHead` and `Stream.runLast` return `Option`. `runLast` waits for the -stream to complete, so do not use it on open-ended streams. +Use `runCollect`, `mkString`, and `mkUint8Array` only when output is finite and +bounded. Apply `take` first when a test or caller needs a bounded prefix. -## Transform Per Element Or Per Chunk Deliberately +`runHead` can stop after one element. `runLast` must wait for completion and +therefore never finishes for a healthy infinite stream. -Use element operators for ordinary domain logic. +## Transform Elements, Arrays, And Windows Deliberately + +Use element operators for domain logic: ```ts const enriched = orders.pipe( @@ -121,36 +136,49 @@ const enriched = orders.pipe( ) ``` -Use chunk-aware operators only when chunk boundaries matter or performance is -worth the extra complexity: `Stream.mapArray`, `Stream.mapArrayEffect`, -`Stream.runForEachArray`, `Stream.grouped`, and `Stream.groupedWithin`. +Use array-aware operators only when source chunking or batching matters: + +- `mapArray` and `mapArrayEffect` transform emitted non-empty arrays. +- `runForEachArray` consumes arrays without flattening first. +- `grouped(n)` creates size-bounded batches. +- `groupedWithin(n, duration)` flushes on size or time. +- `bufferArray` preserves source arrays; `buffer` buffers elements and destroys + the original chunking. ```ts const batched = events.pipe( Stream.groupedWithin(100, "1 second"), - Stream.mapEffect((batch) => EventStore.use((store) => store.writeBatch(batch))) + Stream.mapEffect((batch) => + EventStore.use((store) => store.writeBatch(batch)) + ) ) ``` -Do not insert `runCollect` in the middle of a pipeline just to batch values. -Batch with stream operators so backpressure and interruption still work. +Do not insert `runCollect` in the middle of a pipeline merely to batch values. +That ends streaming, retains all prior elements, and changes interruption and +backpressure behavior. -## Bound Concurrency And Buffers +## Bound Concurrency And Know Ordering -`Stream.mapEffect`, `Stream.flatMap`, `Stream.mergeAll`, and related operators -can run work concurrently. Choose a concrete concurrency limit for I/O and only -use `"unbounded"` when the upstream is already tightly bounded. +`mapEffect` defaults to sequential execution. With concurrent execution it +preserves input order unless `{ unordered: true }` is set. ```ts const results = ids.pipe( - Stream.mapEffect((id) => RemoteApi.use((api) => api.fetch(id)), { - concurrency: 8 - }) + Stream.mapEffect( + (id) => RemoteApi.use((api) => api.fetch(id)), + { concurrency: 8 } + ) ) ``` -Set `unordered: true` only when output order does not matter. Concurrent -`Stream.mergeAll` emits values as they arrive. +Use `unordered: true` only when output order is irrelevant and head-of-line +blocking is undesirable. + +`flatMap` has different semantics: with concurrency greater than one, inner +streams are merged and their values arrive in runtime order. It has no ordered +concurrent mode. `mergeAll` likewise emits from whichever active stream +produces first. ```ts const merged = Stream.mergeAll(streams, { @@ -159,21 +187,43 @@ const merged = Stream.mergeAll(streams, { }) ``` -When creating queues, pubsubs, callbacks, broadcasts, or shared streams, avoid -unbounded capacity by default. Pick a bounded `capacity` and a strategy: +Choose a concrete I/O concurrency limit. Use `"unbounded"` only when upstream +cardinality is already tightly bounded and reviewed. + +When processing stateful updates, ask whether concurrent work is valid at all. +Ordered output does not prevent effects themselves from running concurrently. + +## Treat Buffers As A Correctness Choice + +Bound queues, callbacks, pubsubs, shared streams, and explicit buffers by +default. Choose the overflow strategy from the product semantics: + +- `"suspend"` applies backpressure and retains every value. +- `"sliding"` drops older buffered values and keeps recent state. +- `"dropping"` keeps older buffered values and drops new arrivals. + +```ts +const buffered = source.pipe( + Stream.buffer({ capacity: 32, strategy: "suspend" }) +) +``` + +Backpressure works only when the producer can await an effectful offer. An +external synchronous callback cannot pause for `Queue.offer`; with bounded +callback sources, dropped/coalesced values must be acceptable or the external +API must provide its own pause/resume mechanism. -- `suspend` applies backpressure. -- `sliding` keeps newer values and drops older buffered values. -- `dropping` keeps older buffered values and drops newer values. +Capacity is measured in elements for `buffer` and `toQueue`, but in emitted +arrays for `bufferArray` and broadcast internals. Do not treat every +`bufferSize` as the same unit without checking the signature and source. -## Manage Scope And Cleanup +## Manage Scope And Finalization -Streams run resources for the duration of consumption. Use `Stream.scoped` when -the stream requires `Scope`, and use `Stream.ensuring` or -`Effect.acquireRelease` inside constructors to register finalizers. +Streams hold acquired resources for the duration of one consumption. Use +`Stream.scoped` to internalize a `Scope` requirement. ```ts -const resourceStream = Stream.scoped( +const connectionStream = Stream.scoped( Stream.fromEffect( Effect.acquireRelease( Connection.open, @@ -183,114 +233,185 @@ const resourceStream = Stream.scoped( ) ``` -`Stream.broadcast`, `Stream.broadcastN`, `Stream.share`, `Stream.toQueue`, and -`Stream.toPubSub` return scoped effects. Acquire them inside `Effect.scoped` or -inside a layer so the producer and subscribers are finalized. +Use `Stream.ensuring` for a finalizer that should run after every consumption, +regardless of success, failure, or interruption. Use `Effect.acquireRelease` +when cleanup belongs to an acquired handle. -```ts -const program = Effect.scoped( - Effect.gen(function*() { - const shared = yield* updates.pipe( - Stream.share({ capacity: 16, replay: 1 }) - ) +Scoped destructors and sharing operations include: - yield* shared.pipe(Stream.take(1), Stream.runCollect) - }) -) -``` +- `Stream.toPull` +- `Stream.toQueue` and `Stream.toPubSub*` +- `Stream.broadcast`, `broadcastN`, and `share` + +Acquire them inside `Effect.scoped` or a layer. Closing the scope must stop the +producer and release queues, subscriptions, and external handles. -When bridging to web or async protocols, make sure cancellation closes the -underlying handle. `Stream.fromReadableStream` cancels the reader by default. -`NodeStream.fromReadable` can close Node streams when done. +Web readable streams are canceled by default when their Effect stream +finalizes. `NodeStream.fromReadable` can close Node streams on completion. Keep +those defaults unless ownership belongs elsewhere. ## Bridge Callback APIs Safely -Use `Stream.fromEventListener` for event targets when it fits. It registers and -removes the listener for the stream lifetime. +Use `fromEventListener` for EventTarget-like APIs. It removes the listener when +the stream ends. ```ts -const clicks = Stream.fromEventListener(button, "click", { - passive: true, - bufferSize: 16 -}) +const clicks = Stream.fromEventListener( + button, + "click", + { passive: true, bufferSize: 16 } +) ``` -Use `Stream.callback` for custom callback APIs. Register cleanup with -`Effect.acquireRelease`, and signal completion with the queue API instead of -leaving consumers waiting forever. +`fromEventListener` exposes `bufferSize` but not an overflow strategy. Its +synchronous listener cannot suspend; when a bounded buffer is full, a new event +may be rejected. Use `Stream.callback` when sliding or dropping behavior must be +chosen explicitly. + +Use `Stream.callback` when registration, error, completion, or overflow behavior +needs explicit control. ```ts -const messages = Stream.callback((queue) => - Effect.acquireRelease( - Effect.sync(() => { - const unsubscribeMessage = socket.onMessage((message) => { - Queue.offerUnsafe(queue, message) - }) - - const unsubscribeClose = socket.onClose(() => { - Queue.endUnsafe(queue) - }) - - return { unsubscribeClose, unsubscribeMessage } - }), - ({ unsubscribeClose, unsubscribeMessage }) => +const messages = Stream.callback( + (queue) => + Effect.acquireRelease( Effect.sync(() => { - unsubscribeMessage() - unsubscribeClose() - }) - ), + const unsubscribeMessage = socket.onMessage((message) => { + Queue.offerUnsafe(queue, message) + }) + + const unsubscribeClose = socket.onClose(() => { + Queue.endUnsafe(queue) + }) + + const unsubscribeError = socket.onError((error) => { + Queue.failCauseUnsafe(queue, Cause.fail(error)) + }) + + return { + unsubscribeClose, + unsubscribeError, + unsubscribeMessage + } + }), + (subscriptions) => + Effect.sync(() => { + subscriptions.unsubscribeMessage() + subscriptions.unsubscribeClose() + subscriptions.unsubscribeError() + }) + ), { bufferSize: 64, strategy: "sliding" } ) ``` -Prefer effectful `Queue.offer` when producer code is already inside Effect and -can honor backpressure. Use `Queue.offerUnsafe` only from external synchronous -callbacks where an Effect cannot be yielded. +Signal normal completion with `Queue.endUnsafe` and failure with +`Queue.failCauseUnsafe(queue, Cause.fail(error))`; otherwise consumers can wait +forever. Register every external cleanup through the supplied scope. + +Use effectful `Queue.offer` when producer code is already inside Effect and can +honor backpressure. Use `offerUnsafe` only at a synchronous callback boundary, +and choose an overflow policy that remains correct if it cannot enqueue. + +## Choose Sharing Semantics Explicitly + +Several consumers of a plain stream each run an independent producer. Use the +sharing primitive that matches subscriber lifetime: + +| Need | Primitive | +| --- | --- | +| Dynamic, reference-counted subscribers | `Stream.share` | +| Dynamic subscribers to one scoped pubsub producer | `Stream.broadcast` | +| Fixed number of consumers that must all subscribe before start | `Stream.broadcastN` | +| Competing work consumers | `Queue` / `Stream.toQueue` | +| Every subscriber receives each published event | `PubSub` | + +`share` starts upstream for the first subscriber and stops it after the last +subscriber leaves. A later subscriber restarts the source unless +`idleTimeToLive` keeps it alive. `replay` controls what a late subscriber can +receive from the shared pubsub; it does not turn the source into a permanent +cache. + +```ts +const program = Effect.scoped( + Effect.gen(function*() { + const shared = yield* updates.pipe( + Stream.share({ capacity: 16, replay: 1 }) + ) + + yield* Effect.all([ + shared.pipe(Stream.runForEach(handleForLeftConsumer)), + shared.pipe(Stream.runForEach(handleForRightConsumer)) + ], { concurrency: "unbounded" }) + }) +) +``` + +`broadcastN` is safer when exactly N consumers must observe a finite source: it +does not start until all N returned streams are subscribed. For dynamic +`broadcast` subscribers, use replay or an external readiness protocol if early +values cannot be missed. -## Use Queues And PubSubs For Boundaries +## Use Queues And PubSubs At Ownership Boundaries -Use `Queue` when one producer coordinates with one or more competing consumers -that pull work. `Stream.toQueue` creates a scoped dequeue and signals completion -with `Cause.Done`; stream failures fail the queue. +`Stream.toQueue` creates a scoped dequeue, feeds it in a child fiber, ends it +with `Cause.Done`, and fails it with the stream failure. ```ts const program = Effect.scoped( Effect.gen(function*() { - const queue = yield* source.pipe(Stream.toQueue({ capacity: 32 })) - const next = yield* Queue.take(queue) - return next + const queue = yield* source.pipe( + Stream.toQueue({ capacity: 32 }) + ) + + return yield* Queue.take(queue) }) ) ``` -Use `PubSub` when each subscriber should receive published events. Use -`Stream.fromPubSub` or `Stream.broadcast` instead of manually copying events -into several queues. +Multiple queue consumers compete for values. Use a PubSub when every subscriber +needs a copy. `Stream.fromQueue` treats `Cause.Done` as normal stream completion; +other queue failures become stream failures. + +Prefer `Stream.broadcast` or `Stream.fromPubSub` over manually copying each +event into multiple queues. -## Provide Services At The Stream Boundary +## Keep Service Requirements Until The Boundary -Service requirements flow through stream types the same way they flow through -`Effect`. Provide a layer to the stream or provide a larger program that runs -the stream. +Stream service requirements compose like Effect requirements. Provide the +smallest layer at a stable application or test boundary. ```ts -const stream = Stream.fromEffect( +const users = Stream.fromEffect( UserApi.use((api) => api.listUsers()) ).pipe( - Stream.flatMap(Stream.fromIterable), - Stream.provide(UserApi.layer) + Stream.flatMap(Stream.fromIterable) +) + +const program = users.pipe( + Stream.runForEach(renderUser), + Effect.provide(UserApi.layer) ) ``` -When converting to web APIs outside Effect, use the service-aware variants: -`Stream.toReadableStreamEffect`, `Stream.toReadableStreamWith`, -`Stream.toAsyncIterableEffect`, or `Stream.toAsyncIterableWith`. +When converting a serviceful stream for non-Effect consumers, capture services +explicitly with `toReadableStreamEffect`, `toReadableStreamWith`, +`toAsyncIterableEffect`, or `toAsyncIterableWith`. The plain conversion +variants work only when the stream requires no services. + +Cancellation of the returned readable stream or async iterator is what closes +the Effect scope. Consumers that abandon a manual iterator must call `return()`; +`for await...of` does this when the loop exits normally. -## Handle Errors In The Pipeline +## Recover At The Correct Level -Use stream error combinators when recovery should continue as a stream: -`Stream.catchTag`, `Stream.catchTags`, `Stream.catchIf`, `Stream.catchCause`, -`Stream.mapError`, and `Stream.retry`. +Use stream combinators when recovery should continue as a stream: + +- `catchTag`, `catchTags`, and `catchIf` for typed recovery +- `catchCause` when defects/interruption are deliberately part of policy +- `mapError` to translate the stream failure channel +- `retry` to restart a failed source +- `result` to expose successes and the first failure as values ```ts const recovered = source.pipe( @@ -303,15 +424,20 @@ const recovered = source.pipe( ) ``` -Use `Effect.catchTag` after a `Stream.run*` call when the whole consumed stream -should fail or recover as one effect. Use `Stream.result` when downstream code -needs successes and the first failure as values; the stream still ends after -that failure. +Use `Effect.catchTag` after `Stream.run*` when the entire consumption should be +handled as one effect. + +`Stream.retry` restarts the source description. Values emitted before failure +can be emitted again after the restart. Retry only when duplicated prefixes and +reacquiring the source are safe, or add an explicit cursor/checkpoint protocol. -## Decode And Encode Streaming Data +`Stream.result` emits success results and then one failure result; it still ends +after that first failure. It is not a way to resume the failed source. -For byte streams, decode text before string operations and split lines with -`Stream.splitLines` so delimiters spanning chunks are handled correctly. +## Decode Incremental Bytes Incrementally + +Decode bytes before string operations, and use `splitLines` so delimiters split +across source arrays are handled correctly. ```ts const lines = responseBytes.pipe( @@ -321,8 +447,8 @@ const lines = responseBytes.pipe( ) ``` -For NDJSON or Msgpack, use the encoding channels and schema-backed variants -instead of hand-parsing inside `Stream.map`. +For NDJSON and Msgpack, use the encoding channels and schema-backed variants +instead of `JSON.parse` in `Stream.map`. ```ts import { Ndjson } from "effect/unstable/encoding" @@ -333,36 +459,61 @@ const events = bytes.pipe( ) ``` -Use `ignoreEmptyLines: true` for NDJSON inputs that may contain blank lines. +Use `ignoreEmptyLines: true` only when blank lines are valid transport noise. +Schema decoding should remain at the trust boundary so malformed input has a +typed failure path. -## Testing Patterns +## Testing -Use `it.effect` and consume the stream in the test. Bound infinite streams with -`Stream.take`, and use `TestClock` for schedules, debounce, throttle, retries, -or `groupedWithin`. +A stream test must consume the stream. Bound infinite streams with `take` and +test finalization as well as output. ```ts -import { strictEqual } from "node:assert" -import { Effect, Fiber, Schedule, Stream, TestClock } from "effect" +import { Effect, Fiber, Schedule, Stream } from "effect" +import { TestClock } from "effect/testing" +import { expect, it } from "vitest" + +it("polls three times", async () => { + const values = await Effect.runPromise( + Effect.gen(function*() { + const fiber = yield* Stream.fromEffectSchedule( + Effect.succeed("tick"), + Schedule.spaced("1 second") + ).pipe( + Stream.take(3), + Stream.runCollect, + Effect.forkChild({ startImmediately: true }) + ) + + yield* TestClock.adjust("2 seconds") + + return yield* Fiber.join(fiber) + }).pipe(Effect.provide(TestClock.layer())) + ) -it.effect("polls three times", () => - Effect.gen(function*() { - const fiber = yield* Stream.fromEffectSchedule( - Effect.succeed("tick"), - Schedule.spaced("1 second") - ).pipe( - Stream.take(3), - Stream.runCollect, - Effect.fork - ) + expect(values).toEqual(["tick", "tick", "tick"]) +}) +``` - yield* TestClock.adjust("2 seconds") +For callback, queue, readable-stream, and sharing tests: - const values = yield* Fiber.join(fiber) - strictEqual(values.length, 3) - })) -``` +- start live consumers before publishing +- assert overflow behavior with a deliberately slow consumer +- close or interrupt the scope and assert external cleanup +- test normal completion and source failure +- use `TestClock` for schedules, debounce, throttle, retry, and time windows + +Avoid real sleeps. A time-controlled test may need a start-immediate fork so the +stream reaches its scheduled suspension before the clock advances. + +## Review Checklist -For queue and callback streams, assert finalization as well as emitted values. -Fork consumers before publishing when the source is live, then interrupt or close -the scope to verify cleanup. +- The source constructor matches the real source shape. +- Every collection is proven finite and bounded. +- Concurrency and output ordering are both intentional. +- Buffer capacity and overflow strategy match loss/backpressure requirements. +- Every acquired handle and shared producer has a scoped finalizer. +- Callback sources signal end/failure and tolerate unsafe-offer overflow. +- Sharing semantics match dynamic, fixed, competing, or fan-out consumers. +- Retry cannot duplicate unsafe work or already emitted values. +- Tests consume the stream and cover time, failure, and cleanup behavior. diff --git a/amplify.yml b/amplify.yml index 8dbd96358..7aa7ee258 100644 --- a/amplify.yml +++ b/amplify.yml @@ -11,6 +11,7 @@ frontend: - echo "VITE_YIELDS_API_URL=$VITE_YIELDS_API_URL" >> packages/widget/.env - echo "VITE_API_URL=$VITE_API_URL" >> packages/widget/.env - echo "VITE_API_KEY=$VITE_API_KEY" >> packages/widget/.env + - echo "VITE_FORCE_BORROW=$VITE_FORCE_BORROW" >> packages/widget/.env build: commands: diff --git a/biome-plugins/no-type-re-exports.grit b/biome-plugins/no-type-re-exports.grit new file mode 100644 index 000000000..6d0d9507f --- /dev/null +++ b/biome-plugins/no-type-re-exports.grit @@ -0,0 +1,9 @@ +language js(typescript, jsx) + +or { + `export type { $exports } from $source` as $re_export, + `export type * from $source` as $re_export, + `export { type $exports } from $source` as $re_export +} where { + register_diagnostic(span=$re_export, message="Import types from the module that defines them instead of re-exporting them.") +} diff --git a/biome.json b/biome.json index 21dc2774d..9b4b9052d 100644 --- a/biome.json +++ b/biome.json @@ -15,7 +15,7 @@ "!**/region-iso-3166-codes.ts", "!**/mockServiceWorker.js", "!**/package.json", - "!**/repos" + "!**/@repos" ] }, "formatter": { @@ -32,10 +32,22 @@ } } }, + "plugins": [ + { + "path": "./biome-plugins/no-type-re-exports.grit", + "includes": [ + "**/packages/widget/src/**", + "!**/packages/widget/src/index.package.ts", + "!**/packages/widget/src/index.bundle.ts", + "!**/packages/widget/src/public-api/index.package.ts", + "!**/packages/widget/src/public-api/index.bundle.ts" + ] + } + ], "linter": { "enabled": true, "rules": { - "recommended": true, + "preset": "recommended", "style": { "noNonNullAssertion": "off", "noParameterAssign": "error", @@ -47,7 +59,8 @@ "noUnusedTemplateLiteral": "error", "useNumberNamespace": "error", "noInferrableTypes": "error", - "noUselessElse": "error" + "noUselessElse": "error", + "noNestedTernary": "error" }, "suspicious": { "noArrayIndexKey": "off", @@ -71,6 +84,75 @@ "test": "recommended" } }, + "overrides": [ + { + "includes": [ + "packages/widget/src/**", + "!packages/widget/src/index.package.ts", + "!packages/widget/src/index.bundle.ts", + "!packages/widget/src/public-api/index.package.ts", + "!packages/widget/src/public-api/index.bundle.ts" + ], + "linter": { + "rules": { + "performance": { + "noBarrelFile": "error" + }, + "style": { + "useExportType": "error", + "noRestrictedImports": { + "level": "error", + "options": { + "patterns": [ + { + "group": ["**/index.package", "**/index.bundle"], + "message": "Public widget entrypoints are outbound-only; import from the defining module." + } + ] + } + } + } + } + } + }, + { + "includes": [ + "packages/widget/src/domain/**", + "packages/widget/src/resources/**", + "packages/widget/src/services/**" + ], + "linter": { + "rules": { + "style": { + "noRestrictedImports": { + "level": "error", + "options": { + "patterns": [ + { + "group": [ + "react", + "react/**", + "react-dom", + "react-dom/**", + "@effect/atom-react", + "@effect/atom-react/**", + "@tanstack/react-query", + "@tanstack/react-query/**" + ], + "message": "Headless domain, resource, and service modules must not import React integrations." + }, + { + "group": ["**/index.package", "**/index.bundle"], + "message": "Public widget entrypoints are outbound-only; import from the defining module." + } + ] + } + } + } + } + } + } + ], "javascript": { "formatter": { "semicolons": "always", diff --git a/docs/adr/0001-wallet-service-owns-wallet-runtime.md b/docs/adr/0001-wallet-service-owns-wallet-runtime.md new file mode 100644 index 000000000..6e60bc38d --- /dev/null +++ b/docs/adr/0001-wallet-service-owns-wallet-runtime.md @@ -0,0 +1,26 @@ +--- +status: accepted +--- + +# WalletService owns the wallet runtime + +Wallet initialization and state were split across keyed atoms, React-fed Solana inputs, and a command service bound back to atom state. `WalletService` will instead own one scoped Wallet Runtime per application-runtime generation: it captures one Wallet Bootstrap Snapshot, constructs Wagmi once, owns a serialized canonical state machine and its resources, and exposes current state plus changes for read-only atom adapters. + +Wallet-related configuration is bootstrap-only and later changes do not rebuild Wagmi. External-provider values remain live through a service-owned snapshot, but changing whether an external provider is present violates the fixed Connector Mode and poisons only that Wallet Runtime. Browser-discovered EVM and Solana connectors may change within the existing config without rerunning bootstrap. + +Solana discovery will move from React providers to a scoped headless runtime using the modern Wallet Standard registry, explicit Phantom, Trust, WalletConnect, and Android Mobile Wallet Adapter fallbacks, and in-place readiness and membership updates. Legacy `navigator.wallets` discovery will not be preserved, and an active adapter will never be hot-swapped. + +## Consequences + +- `WalletService` owns bootstrap, Wagmi and connector streams, external-provider synchronization, lifecycle behavior, command routing, tracking integration, and cleanup. +- A single serialized event loop updates private routing context and publishes atomic runtime snapshots with `Bootstrapping`, `Ready`, `BootstrapFailed`, or `InvariantViolated` phases. +- Wallet commands fail immediately before readiness, capture one state snapshot for their duration, and read no atom-owned binding. +- Recoverable connector and enrichment failures degrade only their state slice; bootstrap failures and invariant violations are terminal. +- Wallet atoms become read-only service adapters and selectors. Initialization-key families, controller, binding, lifecycle, connection, connector, Ledger, Cosmos, and additional-address state ownership move out of atoms. +- Published package APIs remain compatible. Ordinary reconnect, mobile fallback, initial switching, tracking, unsupported-chain handling, and remount behavior remain unchanged. + +## Rejected alternatives + +- Rebuilding Wagmi when dynamic atom keys change, because it couples service lifetime to React/atom publication and permits mixed initialization inputs. +- Keeping canonical wallet state in atoms and binding it into `WalletService`, because commands and UI would retain separate authorities. +- Retaining a React-to-service Solana readiness bridge, because the required connection, discovery, readiness, and cleanup primitives are available headlessly. diff --git a/docs/adr/0002-wallet-bootstrap-defines-service-readiness.md b/docs/adr/0002-wallet-bootstrap-defines-service-readiness.md new file mode 100644 index 000000000..6157439d0 --- /dev/null +++ b/docs/adr/0002-wallet-bootstrap-defines-service-readiness.md @@ -0,0 +1,43 @@ +--- +status: accepted +--- + +# Wallet Bootstrap defines service readiness + +`WalletService` acquisition blocks until Wallet Bootstrap has established its scoped Solana and Wagmi resources, installed internal change observation, completed initial connection behavior, and derived the first Wallet State. Consumers observe service acquisition as loading or failure rather than receiving public bootstrapping, ready, or bootstrap-failed phases; after acquisition, the service exposes only current wallet capabilities and ongoing state changes. Long-lived connector and provider events remain scoped implementation details. This supersedes ADR-0001's non-blocking bootstrap and public four-phase snapshot decisions while retaining service ownership of the Wallet Runtime. + +Wallet Bootstrap awaits reconnect, mobile fallback connection, and initial chain switching, but failures of those wallet operations are recoverable: the service records them and becomes available with the actual resulting Wallet State. Failures to establish Wallet Topology, acquire runtime resources, install state observation, or derive the first Wallet State fail `WalletService` acquisition. + +After acquisition, violating a fixed Wallet Runtime Invariant is terminal for that Wallet Runtime. Ongoing wallet-state observation and subsequent commands fail through the typed Effect error channel with the invariant cause; consumers do not receive an invariant-violated phase. The last Wallet State may be retained for diagnostics, and runtime resources remain owned by and are released with the enclosing scope. + +The internal `WalletService` contract is Effect-native: it exposes current Wallet State as an `Effect`, ongoing Wallet State changes as a `Stream`, and wallet commands as Effects. It does not expose synchronous state getters, lifecycle-bearing runtime snapshots, or mutable Effect primitives. Synchronous access required by a third-party callback is confined to a boundary adapter and does not shape the core service API. + +Replaceable external wallet capabilities are injected through Context services and Layers rather than aggregate adapter arguments, optional operation arguments, or test-only dependency parameters. The capability boundaries cover the browser environment, Wagmi integration, and Solana integration; existing API, configuration, persistence, and tracking services remain dependencies. Pure normalization, routing, comparison, and snapshot functions continue to accept explicit data and are not promoted to services. + +After Wallet Bootstrap, ongoing behavior is implemented as focused scoped stream pipelines rather than a central wallet-runtime event queue and reducer. Wagmi observations feed state enrichment and one authoritative `SubscriptionRef`; Solana discovery feeds serialized connector-membership synchronization; external-provider changes feed serialized provider synchronization; and Wallet State changes feed lifecycle effects. The authoritative reference carries terminal invariant failure as an `Exit`, so state reads, state streams, and commands observe one consistent result without a public lifecycle protocol. + +Post-bootstrap connector and enrichment failures remain local to their state slice: Cosmos additional-address failure omits additional addresses, Ledger and filtered-chain enrichment use their defined defaults, Solana discovery or membership failure retains the last valid membership, and tracking or connector-notification failure does not affect Wallet State. Each recovery is explicit and structurally logged; only fixed-topology invariant violations terminate the Wallet Runtime. + +Each wallet command reads the authoritative runtime result once when it begins and captures one immutable routing context for its duration. Later account, connector, network, or state changes affect subsequent commands and do not reroute an in-flight wallet operation across wallet identities. + +Reconnect serialization is scoped to one Wallet Runtime and uses an effectfully acquired semaphore with automatic permit management. Independent widget instances do not share a module-global reconnect lock. Any future requirement for page-wide Wagmi serialization must be represented as an explicitly shared capability backed by a reproduced constraint and focused test. + +There is no separate consumer-facing `WalletRuntime` abstraction. `WalletService` construction is the scoped top-down composition root, and bootstrap helpers return private resource bundles where useful. State projection, Solana membership, external-provider synchronization, and lifecycle behavior remain focused private modules; the existing `runtime.ts` coordinator and its service-like interface are removed. + +The Wagmi configuration is an immutable, non-null property of the acquired `WalletService`, not a nullable Effect or live runtime snapshot field. React may use a temporary fallback configuration while the service is being acquired, then switches to the single configuration established by Wallet Bootstrap. + +`WalletService` owns and publishes one cohesive Wallet State containing normalized connection, account, chain, and connector-specific details such as Ledger state. Raw Wagmi connection and connector observations are private platform inputs, React atoms are selectors over Wallet State, and command-routing handles remain private alongside the captured state. There is no separate Wallet Projection domain model. + +Wallet Bootstrap captures a normalized Wallet Topology fingerprint. Any later change to topology-defining configuration, including connector mode or connector-construction policy, violates the fixed-topology invariant instead of being silently ignored or rebuilding the runtime. Live external-provider address, chain, supported-chain, and operation values remain updateable while external-provider mode is unchanged; tracking callbacks and other explicitly live non-wallet settings are excluded from topology identity. + +Platform capability services translate foreign asynchronous APIs into Effect-native contracts rather than exposing dependency bags with imperative APIs. Wagmi Promise actions become typed Effects and callback watchers become scoped Streams; Solana registry and adapter events become scoped Streams with service-owned cleanup. Required Promise callbacks and unsubscription machinery remain inside platform implementations, while wallet bootstrap, state, synchronization, lifecycle, and routing modules use only Effects and Streams. + +Wallet construction and platform operations preserve narrowly tagged errors, including their operation or bootstrap stage and original cause, until an explicit policy handles them. Wallet Bootstrap has a typed error channel, recoverable initialization and enrichment errors are matched at their policy boundary, invariant violations remain the typed post-bootstrap terminal error, and unexpected defects remain defects. The design avoids both `unknown`/`never` laundering and a single undifferentiated wallet-error union. + +Wallet and platform resource lifetimes are owned by scoped Layers and scoped Effects. `WalletService` is built with `Layer.scoped`; foreign resources use acquisition/release or service finalizers; long-lived synchronization and lifecycle fibers are scoped; and listener Streams own callback registration and unsubscription. Manual active flags and coordinated cleanup remain only where a foreign boundary cannot be represented directly, and do not leak into wallet orchestration. + +Wallet tests substitute capability Layers and assert service acquisition, typed failures, degradation, state changes, command snapshotting, and scoped cleanup. End-to-end tests retain coverage of external-provider, Solana, lifecycle, and React behavior. Tests do not preserve removed lifecycle phases, pending-event ordering, aggregate adapters, or internal queue mechanics. + +The implementation is divided into deep platform services for environment, Wagmi, and Solana; a top-down bootstrap program; focused initial-connection, Wallet State, external-provider synchronization, routing, and lifecycle modules; and a small `wallet-service.ts` scoped composition root. The existing `runtime.ts`, runtime domain snapshot, aggregate adapters, and central event protocol are removed rather than redistributed across similarly shallow files. + +The refactor is delivered as one atomic cutover. Platform services, Wallet Bootstrap, authoritative Wallet State, synchronization pipelines, `WalletService`, React adapters, and tests change together, and the obsolete runtime is deleted in the same change. No staged compatibility architecture or parallel wallet authority is introduced. diff --git a/docs/adr/0003-one-widget-instance-per-browser-document.md b/docs/adr/0003-one-widget-instance-per-browser-document.md new file mode 100644 index 000000000..f4e73bc30 --- /dev/null +++ b/docs/adr/0003-one-widget-instance-per-browser-document.md @@ -0,0 +1,11 @@ +--- +status: accepted +--- + +# One Widget Instance per browser document + +StakeKit Widget supports at most one concurrently mounted Widget Instance per browser document. Concurrent mounts are rejected because document-wide wallet discovery, translation state, and host integration resources make reliable isolation costly and fragile; a host must unmount the active Widget Instance before mounting another. + +The public embedding root owns a document-level claim with a stable identity shared across separately bundled widget copies. A second mount fails fast without disturbing the active Widget Instance, and the bundled renderer exposes unmounting so the claim can be released for a sequential mount. + +This supersedes ADR-0002's statement that independent widget instances are supported. Concurrency machinery and tests whose sole purpose is concurrent Widget Instance isolation are removed, while scoped lifetimes, sequential-remount behavior, and concurrency control required inside the single supported Widget Instance remain. diff --git a/docs/adr/0004-effect-and-atom-own-application-logic.md b/docs/adr/0004-effect-and-atom-own-application-logic.md new file mode 100644 index 000000000..3844dc44a --- /dev/null +++ b/docs/adr/0004-effect-and-atom-own-application-logic.md @@ -0,0 +1,31 @@ +--- +status: accepted +--- + +# Effect and Atom own application logic + +New and materially refactored widget code places business state, transitions, asynchronous orchestration, retries, concurrency, failures, and resource lifetimes in Effect and Effect Atom. React is a view adapter that reads Atom-derived state and dispatches user intent; `useEffect` is reserved for unavoidable React, DOM, router, or third-party lifecycle boundaries that cannot be represented safely as a scoped Effect or lifecycle Atom. + +This trades familiar component-local hook orchestration for one testable application-logic boundary with typed failures and scoped cleanup. It does not require unrelated existing React effects to be migrated, but code touched by a feature or refactor must not introduce or preserve React-owned application logic merely for local convenience. + +React may still own synchronous presentation state with no domain meaning, asynchronous behavior, persistence, route lifetime, or cross-component coordination, such as focus, hover, temporary disclosure, and element references. Derived domain values, validation, user-intent state, loading and failure state, and coordinated view state remain application logic and belong in Effect Atom. + +React may declare that a lifecycle Atom is mounted when a resource genuinely follows view or route visibility, but the Atom or scoped Effect owns acquisition, interruption, and finalization. Resources belonging to the whole Application Runtime Generation are scoped there rather than indirectly owned by a component mount. + +React event handlers are synchronous adapters: they normalize UI input and dispatch an Atom command. They do not run or await Effects, call asynchronous APIs, sequence workflow changes or retries, or clean up domain resources; asynchronous browser operations also belong behind Effect-backed Atom commands. + +Deterministic constructors, transitions, invariant checks, and projections remain plain TypeScript rather than being wrapped ceremonially. Effect Atom owns reactive state and commands; Effect owns typed asynchronous work, dependencies, concurrency, failures, and scoped resources. + +Feature facades expose read-only view Atoms and writable command Atoms while retaining private mutable storage. React may consume them directly or through zero-logic convenience hooks, but those hooks do not derive business values, branch on domain variants, or orchestrate commands. + +Effect-backed resources and command Atoms own loading, typed failure normalization, retry eligibility, and stale-result suppression. React pattern-matches published view state and dispatches Retry rather than catching promises, translating raw exceptions, or inferring asynchronous state with local flags. + +Application-logic modules do not import React, and materially touched view adapters do not use `useEffect`. An unavoidable external lifecycle exception is isolated and documented in a named boundary adapter rather than becoming precedent for feature orchestration. + +The document-level Widget Instance claim prefers scoped Effect ownership exposed through an Atom lifecycle. If safe React mount and unmount semantics require a hook, one isolated embedding-boundary adapter may bridge only claim acquisition and release; it cannot own wallet or feature behavior. + +New and materially refactored feature resources use Effect services and resources exposed through Atom rather than React Query, hook-owned fetching, or Promise caches. Existing unrelated React Query usage is not migrated solely because of this decision. + +Feature Effects run through the existing scoped application or wallet Atom runtimes and injected Effect services. Feature modules do not construct ad hoc runtimes or call `Effect.runPromise`; runtime generations own interruption and cleanup. + +Third-party capabilities prefer headless Effect services. When an API is genuinely React-only, a named boundary adapter normalizes its snapshots and callbacks into Atom; decisions, asynchronous sequencing, error policy, and cleanup beyond the library subscription remain in Effect and Atom. diff --git a/docs/adr/0005-scope-classic-flow-by-flow-session.md b/docs/adr/0005-scope-classic-flow-by-flow-session.md new file mode 100644 index 000000000..149056925 --- /dev/null +++ b/docs/adr/0005-scope-classic-flow-by-flow-session.md @@ -0,0 +1,19 @@ +--- +status: accepted +--- + +# Scope Classic Flow by Flow Session + +Classic Transaction Flow state is owned by one non-`keepAlive` Session module mounted by a stable Review, Steps, and Complete route tree. A Widget-runtime intake store assigns each explicit Start a monotonic local epoch, including structurally equal intake, but that epoch is confined to keyed React remounting, targeted store cleanup, and suppression of stale shared-world outputs such as routing and tracking. It is not a domain identity carried through command inputs, failures, navigation outcomes, resources, or Transaction Workflow handoffs. + +The Session retains immutable tagged Enter, Exit, Manage, and Activity Resume intake and only one mutable execution-action handoff. Each Review route mount creates a fresh Review scope that owns Action Preview and forward navigation. Continue promotes the reviewed candidate into the Session handoff. Each Steps-and-Complete route mount creates a fresh Execution scope that captures the handoff, mounts the Transaction Workflow module's dedicated root Atom for the scope's whole lifetime, and owns cancellation navigation. The workflow therefore survives the page-consumer gap between Steps and Complete without making its view projection a lifecycle handle. Entering any later Review scope clears the handoff and permanently ends the previous Execution Attempt. + +Session, Review, and Execution use separate narrow scoped capabilities rather than a combined nullable context. All scopes use the existing application Atom registry; no nested registry is introduced. Atom disposal owns asynchronous interruption and cleanup, while the Session handoff makes Review-to-Execution correctness independent of child cleanup ordering. + +## Rejected alternatives + +- Relying on intake object references plus a `WeakMap` for React keys, because the lifecycle distinction is real and should be explicit in the intake store. +- Removing every epoch check, because an animated exiting tree can still target the shared intake store or router after replacement. +- Keeping Review and Execution in one literal Atom lifetime, because `idleTTL(0)` schedules disposal and cannot guarantee a fresh Review across an immediate unmount/remount. +- Storing a Review-attempt generation in Session state, because route mount identity belongs to the React subtree and a fresh scoped Atom already represents it. +- Retaining the global `keepAlive` facade and branded Classic Transaction Flow Identity, because they spread lifetime coordination across otherwise scoped state and duplicate router and action-derived facts with explicit phases. diff --git a/docs/adr/0006-scope-transaction-workflows-by-execution.md b/docs/adr/0006-scope-transaction-workflows-by-execution.md new file mode 100644 index 000000000..09ce37ced --- /dev/null +++ b/docs/adr/0006-scope-transaction-workflows-by-execution.md @@ -0,0 +1,7 @@ +--- +status: accepted +--- + +# Scope Transaction Workflows by execution + +Each time an action enters execution, its immutable Transaction Workflow Input creates a fresh Transaction Workflow scoped Atom. That scoped Atom alone owns the workflow lifetime; releasing it disposes the machine, resets its passive read capabilities, and revokes retained commands so none can extend or revive the execution. Structurally equal inputs never share a workflow. Classic and Borrow adapters compose their distinct projections and completion behavior around the shared module, permit brief isolated overlap while an older route scope exits, and suppress stale UI-ownership outputs such as routing and handoff cleanup without suppressing transaction-truth outputs such as tracking and resource invalidation; no application-global workflow claim or `Atom.family` identity is introduced. diff --git a/docs/adr/0007-separate-transaction-flow-modules-by-journey.md b/docs/adr/0007-separate-transaction-flow-modules-by-journey.md new file mode 100644 index 000000000..2383153e7 --- /dev/null +++ b/docs/adr/0007-separate-transaction-flow-modules-by-journey.md @@ -0,0 +1,7 @@ +--- +status: accepted +--- + +# Separate Transaction Flow modules by journey + +Classic Transaction Flow and Borrow Transaction Flow are separate feature modules that own their distinct Flow Session intake, preparation, projections, navigation, and abandonment behavior while adapting the shared Transaction Workflow module for execution. The Borrow market, form, position, and resource module starts Borrow Transaction Flow through immutable intake and observes read-only flow outcomes to reset its own state, so the flow never imports back into Borrow; the current routed Flow Session remains authoritative without a generic flow implementation or application-global Flow Session coordinator. diff --git a/docs/adr/0008-authoritative-resources-own-remote-read-caching.md b/docs/adr/0008-authoritative-resources-own-remote-read-caching.md new file mode 100644 index 000000000..eb651a9c7 --- /dev/null +++ b/docs/adr/0008-authoritative-resources-own-remote-read-caching.md @@ -0,0 +1,23 @@ +--- +status: accepted +--- + +# Authoritative Resources own remote read caching + +Cacheable remote reads are owned by a new top-level `src/resources` tier. Each canonical remote fact has one named, typed Authoritative Resource whose explicit key, Atom identity, decoding, pagination, freshness, polling, retry, typed failure, and semantic invalidation policy are shared by every feature. Resources cache canonical decoded facts; features bind current context and derive selection, visibility, summaries, and UI read models. Exact semantic requests always share one Widget Instance registry-scoped cache, while cross-request entity normalization is introduced only when a resource proves that response completeness, freshness, and absence semantics are compatible. + +Backend access is divided into coarse capability ports. Yield and Borrow expose resource-source capabilities for cacheable reads and operation capabilities for mutations and transient execution work; Legacy exposes only a resource-source capability until it gains a real mutation. `src/resources` is the sole cache owner consuming resource-source ports. Wallet Bootstrap is the named exception: it may acquire enabled networks and the optional initial Yield directly because those one-time reads construct the wallet runtime before feature resources exist. Feature command atoms may consume operation ports, and deeper workflow operation modules are introduced only when they hide real coordination. `ApiTransportService` and generated clients remain private transport infrastructure, while the broad `YieldApiService`, `LegacyApiService`, and `BorrowApiService` contracts are removed. Dependency checks prohibit feature read models from bypassing Authoritative Resources. + +The enforced import graph allows `YieldOperations` only from Classic Review and Transaction Workflow, and `BorrowOperations` only from Borrow Review and Transaction Workflow. Application runtime composition and tests may construct every capability. Resource-source ports are otherwise restricted to Authoritative Resources, while approved domain schema adapters may import generated schema artifacts without gaining access to generated runtime clients. + +Commands remain with the feature or workflow that owns their intent and publish semantic invalidation keys rather than refreshing concrete resource Atoms. Resource modules own transport pagination, but the semantic read contract—not the backend endpoint—determines the resource interface. The same endpoint may therefore support distinct demand-driven Pull, complete-collection, bounded-summary, and point-lookup resources. Remotely paginated product lists default to demand-driven loading through one shared `Atom.family` Pull per semantic query; equivalent consumers share its accumulated progress, and feature facades only project its values and forward Pull or Refresh. Complete collection is explicit and justified by a consumer that requires completeness. Native Atom and Stream lifecycle semantics own continuation, accumulation, failure, and refresh from the first page; resources do not add page caches, offset atoms, locks, replay state, or custom refresh coordination. Resource freshness and lifecycle policies are not caller-configurable. Migration is performed as completed resource-by-resource vertical slices; temporary adapters must delegate to the new authority and are removed with the old fetching implementation before the next slice begins. + +## Rejected alternatives + +- One global API-atom module or generic resource registry, because it would combine unrelated identities, lifetimes, errors, and invalidation rules behind a shallow interface. +- Feature-owned remote resources, because the same remote fact can acquire several Atom identities, freshness policies, and caches across Earn, Portfolio, Activity, and other features. +- One transport capability per endpoint or resource, because it would mirror the resource tier with shallow pass-through modules. Coarse read and operation ports provide enforceable access without that duplication. +- Caller-configurable cache policies and process-global normalized caches, because they permit consumers to fight over freshness and allow data to outlive the Widget Instance and API configuration that produced it. +- One eagerly completed collection per paginated endpoint followed by in-memory slicing, because it overfetches product lists and erases the backend continuation contract. +- A custom page cache or pagination coordinator beneath semantic Pull resources, because native Atom and Stream ownership already shares progress and defines refresh and failure behavior without another state machine. +- A big-bang rewrite or permanent compatibility layer, because both would increase the period in which two implementations can claim authority for the same remote fact. diff --git a/docs/adr/0009-earn-state-resolves-selection-and-readiness.md b/docs/adr/0009-earn-state-resolves-selection-and-readiness.md new file mode 100644 index 000000000..564d33c48 --- /dev/null +++ b/docs/adr/0009-earn-state-resolves-selection-and-readiness.md @@ -0,0 +1,27 @@ +--- +status: accepted +--- + +# Earn state resolves selection and readiness + +Earn exposes one Atom-owned application-state boundary that resolves Earn Selection, Earn Readiness, capabilities, and stage-specific failure from user intent plus Authoritative Resources. React renders that contract and dispatches synchronous commands; it does not infer loading, empty, or failure semantics from individual resources. This applies ADR-0004's application-logic ownership while preserving ADR-0008's rule that canonical remote reads, caching, pagination, refresh, and stale-result suppression remain owned by Authoritative Resources. + +The resolver reads Authoritative Resources lazily in dependency order, while starting independent initialization and positions reads together. Each resource `AsyncResult` is normalized once into an unavailable, failed, or usable observation; a usable observation retains whether a same-key refresh is waiting. The published view exposes normalized token, yield, and positions snapshots plus only the operational pagination atoms React needs. A blocking failure carries its exact retry target atom, so retry routing does not reconstruct resource identity outside the resolver. + +The `EarnYield` response schema owns Earn Mechanic Argument projection and validation. It first decodes every field through the generated API schema, then resolves the typed API field array into a name-keyed domain record containing only the six arguments the Widget consumes: amount, provider ID, Tron resource, validator address, validator addresses, and subnet ID. Consumed fields also validate the canonical name-to-type variants produced by the Yield API: amount, provider ID, validator address, and validator addresses are strings; Tron resource is an enum; and subnet ID is a number. Amount decoding preserves the complete `-1/-1` force-max pair but normalizes a `-1` maximum paired with a non-negative minimum to an unbounded maximum. The domain projection drops wire-only metadata such as `name`, `type`, and labels, retaining only required flags, amount bounds, and options consumed by the Widget. The Widget trusts the API contract to provide unique argument names. Valid unconsumed API fields are projected away; malformed API fields or invalid consumed domain values are response-decode failures, so tolerant directory responses omit only that yield while a direct opportunity request follows ordinary resource-failure handling. Earn state consumes this typed model and does not carry or retry a separate form-contract failure. + +The machine distinguishes first acquisition from later refresh. A required first acquisition without a usable value blocks readiness and exposes one structured, stage-specific failure with a targeted retry command. A later refresh retains the last successful selection and readiness, exposes its failure non-blockingly through the responsible resource, and does not enter the blocking failure state. Successful authoritative results reconcile and commit invalid selections so removed data cannot later cause a hidden intent to snap back. + +Optional preference enrichment is not a required acquisition: preferred-token discovery reads the complete Legacy token directory, treats an unavailable directory result as a missing preference candidate, and leaves the Authoritative Resource's error semantics unchanged. All actual token candidates are still intersected with the active category and API-key-enabled yield scope. + +Earn Initialization seeds selection once per Widget Instance and never becomes a permanent fallback. For an account-targeted deep link, committing a resolved selection does not consume initialization until the initial Wallet Scope Owner exists; this lets the required owner reset intent and resolve the target without re-arming it. Later Wallet Scope Owner changes reset Earn intent but do not rerun Earn Initialization, while additional-address-only changes refresh dependent facts without resetting intent. A committed yield ID remains a resource seed while selected so catalog re-keying cannot transiently replace it with a fallback. These lifecycle rules favor deterministic, safe selection over preserving form progress across wallet owners. + +The detailed status vocabulary, precedence rules, loading dependencies, form invariants, pagination behavior, and verification matrix are specified in `.scratch/earn-state-machine/spec.md`. + +## Considered options + +- Let React combine resource loading and failure flags. Rejected because it creates a second, presentation-owned state machine and conflicts with ADR-0004. +- Preserve raw mechanic field arrays and validate selected yields in the resolver. Rejected because invalid remote data would cross the response seam and require a second error contract inside Earn state. +- Treat every resource failure as blocking. Rejected because a failed refresh with a usable prior value should not discard a coherent Earn Selection. +- Keep invalid user intent behind a fallback projection. Rejected because removed options could unexpectedly reappear as the selection after a later refresh. +- Reapply initialization after every wallet-owner change. Rejected because initialization is a Widget Instance input, not a persistent selection preference. diff --git a/docs/adr/0010-effect-datetime-owns-application-time.md b/docs/adr/0010-effect-datetime-owns-application-time.md new file mode 100644 index 000000000..6ed7b6dc8 --- /dev/null +++ b/docs/adr/0010-effect-datetime-owns-application-time.md @@ -0,0 +1,28 @@ +--- +status: accepted +--- + +# Effect DateTime owns application time + +Widget application and test code represents instants with Effect `DateTime`, intervals with `Duration`, and effectful current time with `Clock` or `DateTime.now`. Native JavaScript `Date`, `date-fns`, and Effect APIs that convert application values to native `Date` are not part of the application model. Generated code and third-party library internals remain outside this decision. + +API date-time strings are decoded at the boundary into UTC `DateTime` values. Required invalid timestamps reject their containing object. Invalid optional timestamps become `undefined`, and invalid nullable timestamps become `null`; both tolerant cases emit a structured warning containing the operation and field but not the rejected value. Strings are produced again only at serialization boundaries. + +Presentation explicitly supplies locale and converts UTC instants to the browser's local time zone. Relative labels share one observed, scoped minute ticker. Human durations use floored display buckets: less than one minute, minutes below one hour, hours below one day, days below 31 days, approximate months below 365 days, and approximate years thereafter. Domain thresholds use named durations such as `Duration.days(7)` and remain independent of presentation wording. + +The synchronous Cosmos WalletConnect adapter may use `DateTime.nowUnsafe()` because its inherited library callback cannot yield an Effect. No other handwritten widget or test code may use unsafe wall-clock access. An AST rule enforced by the normal lint command prevents native `Date`, native-Date interop schemas/conversions, and unsafe DateTime clock helpers outside that adapter. + +## Consequences + +- Domain models, history points, workflow gates, connector expiries, tests, and UI helpers share one time representation. +- Time-dependent application behavior is controllable by Effect runtimes, while pure comparisons accept an explicit `now`. +- A seven-day Activity resume gate is owned by the Classic Transaction Flow and blocks confirmation at the command boundary; React only renders the published state. +- Date formatting has no global locale mutation. Translation owns duration wording and callers supply the active locale for absolute formatting. +- Library code may internally use native `Date`; application lint does not inspect dependencies or generated sources. + +## Rejected alternatives + +- Keeping `date-fns` for display helpers, because it preserves a second time model and native `Date` conversions. +- Treating all malformed timestamps as fatal, because optional metadata must not discard an otherwise valid API object. +- Computing current time in React, because domain gates and refresh lifetimes belong below the view layer. +- Encoding elapsed domain thresholds as display units, because labels such as days and months are presentation buckets rather than exact business intervals. diff --git a/docs/adr/0011-feature-facades-hide-atom-identity.md b/docs/adr/0011-feature-facades-hide-atom-identity.md new file mode 100644 index 000000000..9f84ed9f1 --- /dev/null +++ b/docs/adr/0011-feature-facades-hide-atom-identity.md @@ -0,0 +1,14 @@ +--- +status: accepted +--- + +# Feature facades hide Atom identity + +Feature facades expose stable read-only view Atoms and writable command Atoms while retaining mutable state and dynamic resource Atom identities privately. Published view values contain no nested Atoms, Atom factories, or retry, pagination, refresh, or command callbacks. A facade resolves the active resource internally and forwards user intent through stable commands, so React reads views and dispatches intent without routing an Atom obtained from one subscription into another. + +Deterministic calculations remain plain TypeScript, Authoritative Resources retain canonical read and cache ownership, and feature modules own the reactive composition between them. This refines ADR-0004 and supersedes only ADR-0009's allowance for operational pagination and exact retry-target Atoms to cross the Earn facade; ADR-0009's Earn Selection, readiness, failure, and initialization decisions remain in force. + +## Rejected alternatives + +- Expose dynamic resource Atoms through view values, because callers then own resource identity and recreate an Atom-to-React-to-Atom binding. +- Publish one callback-rich aggregate view model, because it couples unrelated capabilities, hides command ownership, and makes every consumer subscribe to the aggregate. diff --git a/docs/adr/0012-application-runtime-owns-application-navigation.md b/docs/adr/0012-application-runtime-owns-application-navigation.md new file mode 100644 index 000000000..0abe71516 --- /dev/null +++ b/docs/adr/0012-application-runtime-owns-application-navigation.md @@ -0,0 +1,22 @@ +--- +status: accepted +--- + +# Application Runtime Generation owns application navigation + +Application-owned navigation decisions are executed through a headless `WidgetNavigation` module in the application Atom runtime. A scoped `ApplicationRouter` Layer owns the memory router for one Application Runtime Generation, and `WidgetNavigation` is constructed directly from that service. Commands use canonical absolute widget paths and state whether navigation pushes, replaces, or goes back, plus whether normal widget scroll-reset policy applies. + +React obtains the runtime-owned router synchronously through an internal Atom and passes it to `RouterProvider`; feature modules do not receive the raw router. Closing the Application Runtime Generation disposes the router, and replacing API identity starts a fresh router with fresh memory history. Live settings changes within the same generation preserve the router. + +Workflow commands and transition events invoke navigation directly after their ownership and stale-result checks. Derived view Atoms remain passive, and application logic no longer publishes navigation outcomes for React effects or `` adapters to apply. Declarative route guards, view-local tabs and back controls, route reads used only for presentation, and external URL navigation remain view concerns. + +This refines ADR-0004's router-boundary allowance and the routing responsibilities in ADR-0005 through ADR-0007. Flow Session and Execution Attempt scopes still own their navigation decisions and stale-output suppression; the Application Router owns only router construction, memory history, subscription, and disposal, while `WidgetNavigation` owns application navigation commands and scroll-reset policy. + +## Rejected alternatives + +- Publish navigation outcomes for React to observe and apply, because it introduces an Atom-to-React command bridge and duplicate-delivery coordination. +- Expose the router instance to feature modules, because it leaks router lifecycle and capabilities instead of providing one narrow navigation interface. +- Construct or retain the router in React state, because router construction and disposal belong to the Application Runtime Generation and data routers should be created outside the React tree. +- Use a module-global router, because sequential Widget Instance mounts require fresh memory history. +- Retain a production navigation adapter between `ApplicationRouter` and `WidgetNavigation`, because it adds no independent policy or lifecycle and duplicates the service boundary. +- Preserve route-relative workflow destinations, because resolving them depends on React route context and makes command tests ambiguous. diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 000000000..9c377c46a --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,35 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root. +- **`docs/adr/`** — read ADRs that touch the area you're about to work in. + +If these files don't exist, **proceed silently**. Don't flag their absence or suggest creating them upfront. The `/domain-modeling` skill creates them lazily when terms or decisions are resolved. + +## File structure + +This repo uses a single-context layout: + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-example-decision.md +│ └── 0002-another-decision.md +└── packages/ +``` + +## Use the glossary's vocabulary + +When output names a domain concept—in an issue title, refactor proposal, hypothesis, or test name—use the term defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. + +If a needed concept isn't in the glossary, reconsider whether the language belongs to the project or note the genuine gap for `/domain-modeling`. + +## Flag ADR conflicts + +If output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (example decision) — but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 000000000..82732f555 --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,30 @@ +# Issue tracker: Local Markdown + +Issues and specs (you may know a spec as a PRD) for this repo live as markdown files in `.scratch/`. + +## Conventions + +- One feature per directory: `.scratch//` +- The spec is `.scratch//spec.md` +- Implementation issues are one file per ticket at `.scratch//issues/-.md`, numbered from `01` — never a single combined tickets file +- Triage state is recorded as a `Status:` line near the top of each issue file (see `triage-labels.md` for the role strings) +- Comments and conversation history append to the bottom of the file under a `## Comments` heading + +## When a skill says "publish to the issue tracker" + +Create a new file under `.scratch//` (creating the directory if needed). + +## When a skill says "fetch the relevant ticket" + +Read the file at the referenced path. The user will normally pass the path or issue number directly. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a file with one **child** file per ticket. + +- **Map**: `.scratch//map.md` — the Notes / Decisions-so-far / Fog body. +- **Child ticket**: `.scratch//issues/NN-.md`, numbered from `01`, with the question in the body. A `Type:` line records the ticket type (`research`/`prototype`/`grilling`/`task`); a `Status:` line records `claimed`/`resolved`. +- **Blocking**: a `Blocked by: NN, NN` line near the top. A ticket is unblocked when every file it lists is `resolved`. +- **Frontier**: scan `.scratch//issues/` for files that are open, unblocked, and unclaimed; first by number wins. +- **Claim**: set `Status: claimed` and save before any work. +- **Resolve**: append the answer under an `## Answer` heading, set `Status: resolved`, then append a context pointer (gist + link) to the map's Decisions-so-far in `map.md`. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 000000000..2411d9b46 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,15 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the status strings used in this repo's issue tracker. + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role, use the corresponding status string from this table. + +Edit the right-hand column to match whatever vocabulary you actually use. diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 000000000..71d29c8f4 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,112 @@ +# Releases + +`@stakekit/widget` has two npm release paths: + +- Stable releases are produced from `main` through Release Please and use the + npm `latest` tag. +- Canary releases are manually requested from a selected branch for internal + StakeKit QA and use the npm `canary` tag. + +Canaries are not a public support channel and are never promoted into stable +releases. + +## Publishing authority + +Repository writers may request and publish a canary workflow run without a +deployment-environment approval. Stable npm publication uses the `production` +GitHub environment, which must require approval from a release maintainer. + +Both release paths publish from the existing +`.github/workflows/release.yml` workflow so npm trusted publishing remains +bound to one workflow identity. The npm trusted publisher must not restrict +that workflow identity to a GitHub environment: the stable job supplies the +`production` environment claim, while the canary job intentionally has no +environment. + +## Stable releases + +A push to `main` runs Release Please. Release Please maintains the release pull +request and creates the package Git tag and GitHub release when that pull +request is merged. + +The `production` environment approval gates the stable release job. + +The workflow publishes only when all of these conditions hold: + +1. A GitHub release exists for `@stakekit/widget@`. +2. That exact package version is not already present on npm. +3. The tagged source builds successfully. + +Stable releases publish without an alternate dist-tag and therefore update +`latest`. + +## Canary releases + +To request a canary: + +1. Open **Actions** in GitHub. +2. Select the **Release** workflow. +3. Choose **Run workflow**. +4. Select the branch to test. +5. Start the workflow. + +The selected branch must contain the manual release workflow and its +`packages/widget/package.json` version must equal the version currently +published under npm `latest`. Rebase or merge the current stable baseline +before requesting a canary from a stale branch. + +For a branch based on `0.0.282`, workflow run `418` publishes: + +```text +@stakekit/widget@0.0.283-canary.418 +``` + +The workflow: + +1. Checks out the exact selected commit. +2. Reads the current npm `latest` version. +3. Derives `next patch + canary + GITHUB_RUN_NUMBER`. +4. Updates `packages/widget/package.json` only in the runner. +5. Builds the widget and runs its unit and DOM tests. +6. Inspects the package contents. +7. Publishes with the npm `canary` dist-tag. +8. Records the branch, commit, exact version, and install commands in the + workflow summary. + +It does not commit the generated version, create a Git tag, or create a GitHub +release. + +## QA usage + +Use the moving channel only to obtain the newest published canary: + +```sh +pnpm add @stakekit/widget@canary +``` + +Record and pin the exact version in bug reports, test environments, and QA +sign-off notes: + +```sh +pnpm add @stakekit/widget@0.0.283-canary.418 +``` + +Each successful canary publication moves the `canary` dist-tag. The moving tag +is convenient but is not a reproducible test reference. + +## Retries and failures + +- Rerun the same GitHub workflow run when its commit has not changed. The run + retains its canary version, and the npm existence check makes a + post-publication rerun safe. +- Start a new workflow run after changing the branch or selecting a different + commit. The new run number intentionally creates a new canary version. +- If the branch version is behind npm `latest`, rebase or merge the current + stable baseline and start a new run. +- If build or tests fail, fix the selected branch and start a new run. + +## Stable release after QA + +Canary QA sign-off supplies evidence for a later stable release; it does not +promote or retag the canary artifact. Merge the intended changes into `main` +and let Release Please produce and publish the independent stable version. diff --git a/mise.lock b/mise.lock index 51b141044..85c22ae77 100644 --- a/mise.lock +++ b/mise.lock @@ -41,5 +41,5 @@ version = "11.14.1" backend = "npm:npm" [[tools."npm:pnpm"]] -version = "10.33.2" +version = "11.12.0" backend = "npm:pnpm" diff --git a/mise.toml b/mise.toml index 8cc2234b3..119a523d8 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,4 @@ [tools] node = "24.15.0" "npm:npm" = "11.14.1" -"npm:pnpm" = "10.33.2" \ No newline at end of file +"npm:pnpm" = "11.12.0" \ No newline at end of file diff --git a/package.json b/package.json index 9487ca9ea..656fdd131 100644 --- a/package.json +++ b/package.json @@ -6,28 +6,35 @@ ], "author": "Petar Todorovic (https://github.com)", "license": "MIT", + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "11.12.0" + } + }, "scripts": { "build": "turbo run build", - "lint": "turbo run lint", + "lint": "turbo run lint //#lint:ast", + "lint:ast": "ast-grep scan --error=no-suppress-all --error=unused-suppression packages/widget --color=never && pnpm run test:ast", + "test:ast": "ast-grep test --skip-snapshot-tests --color=never", "test": "turbo run test", "format": "turbo run format", "clean": "turbo run clean", "prepare": "husky", - "check-hygiene": "rev-dep config run --list-all-issues", - "check": "turbo run lint && pnpm run check-hygiene && turbo run test && turbo run build" + "check-hygiene": "pnpm run check-hygiene:rev-dep && pnpm run check-hygiene:test-only-exports", + "check-hygiene:rev-dep": "rev-dep config run --list-all-issues", + "check-hygiene:test-only-exports": "tsx scripts/check-test-only-exports.ts", + "check": "turbo run lint //#lint:ast && pnpm run check-hygiene && turbo run test && turbo run build" }, "devDependencies": { + "@ast-grep/cli": "catalog:", "@biomejs/biome": "catalog:", "@commitlint/cli": "catalog:", "@commitlint/config-conventional": "catalog:", "husky": "catalog:", + "knip": "catalog:", "rev-dep": "catalog:", + "tsx": "catalog:", "turbo": "catalog:" - }, - "pnpm": { - "patchedDependencies": { - "purify-ts": "patches/purify-ts.patch", - "@stakekit/rainbowkit@2.2.11": "patches/@stakekit__rainbowkit@2.2.11.patch" - } } } diff --git a/packages/examples/with-nextjs/package.json b/packages/examples/with-nextjs/package.json index 03dc05032..a6e778342 100644 --- a/packages/examples/with-nextjs/package.json +++ b/packages/examples/with-nextjs/package.json @@ -18,6 +18,7 @@ "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", + "@typescript/native": "catalog:", "typescript": "catalog:" } } diff --git a/packages/examples/with-vite-bundled/package.json b/packages/examples/with-vite-bundled/package.json index 78a969803..93bd85fbe 100644 --- a/packages/examples/with-vite-bundled/package.json +++ b/packages/examples/with-vite-bundled/package.json @@ -14,7 +14,7 @@ "@stakekit/widget": "workspace:*" }, "devDependencies": { - "typescript": "catalog:", + "@typescript/native": "catalog:", "vite": "catalog:" } } diff --git a/packages/examples/with-vite-bundled/src/typescript.svg b/packages/examples/with-vite-bundled/src/typescript.svg deleted file mode 100644 index 351f16e65..000000000 --- a/packages/examples/with-vite-bundled/src/typescript.svg +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/packages/examples/with-vite/package.json b/packages/examples/with-vite/package.json index c947f694e..c1b24fc58 100644 --- a/packages/examples/with-vite/package.json +++ b/packages/examples/with-vite/package.json @@ -18,7 +18,7 @@ "devDependencies": { "@types/react": "catalog:", "@types/react-dom": "catalog:", - "typescript": "catalog:", + "@typescript/native": "catalog:", "vite": "catalog:", "@vitejs/plugin-react": "catalog:" } diff --git a/packages/widget/.env.example b/packages/widget/.env.example index 77baaa97a..81fe7033e 100644 --- a/packages/widget/.env.example +++ b/packages/widget/.env.example @@ -6,5 +6,6 @@ VITE_FORCE_WALLET_CONNECT_ONLY=false VITE_ENABLE_MSW_MOCK=true VITE_SHOW_QUERY_DEVTOOLS=true VITE_FORCE_ADDRESS= +VITE_FORCE_BORROW= VITE_FORCE_DASHBOARD= -VITE_APP_VARIANT= \ No newline at end of file +VITE_APP_VARIANT= diff --git a/packages/widget/ARCHITECTURE.md b/packages/widget/ARCHITECTURE.md new file mode 100644 index 000000000..aa6058ad8 --- /dev/null +++ b/packages/widget/ARCHITECTURE.md @@ -0,0 +1,242 @@ +# Widget architecture + +## Module ownership + +Production code is organized by ownership rather than by React mechanism: + +- `src/app` owns public-input normalization, runtime construction, provider + composition, and classic/dashboard route composition. +- `src/resources` owns Authoritative Resources: app-runtime-scoped, cacheable + remote reads shared across features. Each remote fact has one named, typed + resource module rather than a global registry. +- `src/services` owns Effect services and side effects: API transport, + persistence, tracking, widget navigation, wallet integration, workflow + execution, and borrow execution. +- `src/features/` owns feature state, contextual read models, command + atoms, React adapters, and screens. A feature exposes cross-feature + collaboration only through an intentional root entry such as `index.ts`, + `support.ts`, `state.ts`, `ui.ts`, or another narrowly named entrypoint. +- `src/domain` owns framework-independent schemas, identifiers, and business + rules. Approved domain schema modules may adapt generated schemas, but domain + code must not depend on React, services, or features. +- `src/shared` owns framework-neutral utilities and genuinely reusable React or + UI primitives. Shared modules must not depend on app, services, or features. + +The retired top-level `hooks`, `providers`, `pages`, `pages-dashboard`, +`components`, `common`, `atoms`, and `borrow` ownership buckets must not be +reintroduced. A hook belongs in its feature's `react` or `ui` area; a provider +belongs in `app/composition` only when it composes the application or adapts a +third-party tree-scoped contract. + +## Dependency direction and public entries + +The intended read direction is: + +`public entry -> app composition/routes -> feature public entries -> resources -> app runtime -> services -> domain/shared` + +Feature commands and workflows may use operation capability services through +the app runtime, but feature read models do not bypass Authoritative Resources +to call read-side API capabilities directly. The owning operation importers are +the Classic and Borrow Flow Session facades plus the Transaction Workflow +operations service. Wallet Bootstrap is the only read-source exception: it +acquires enabled networks and an optional initial Yield while constructing the +wallet runtime, before feature resources are available. + +Feature-to-feature collaboration must use an explicit supported entrypoint; +deep imports into another feature are forbidden. Resources may depend on the +app runtime, resource-source capability services, domain, shared code, and +public types, but never on features or React. Services may depend on other +services, domain, shared code, and generated clients where approved, but never +on resources, React, or features. Rev-dep enforces module direction, +public-entry usage, resource-source importer restrictions, cycles, unresolved +imports, and orphaned modules. Biome confines generated API imports, rejects +React dependencies in Authoritative Resources, and blocks retired architecture +paths. Application runtime composition and tests are permitted to construct +capability layers; approved domain schema adapters may import generated schema +artifacts, but generated runtime clients remain private to `services/api`. + +## Effect services + +Each Effect service keeps its service definition and common layer builders +colocated. Alternative implementations are additional layer builders on the +service or layers defined next to the integrating adapter; contract and default +implementation are not split into files without a concrete reason. + +Backend integration is exposed through coarse capability ports rather than one +broad service or one port per endpoint. Resource-source capabilities contain +cacheable reads and may be consumed only by `src/resources`; operation +capabilities contain mutations and transient execution operations and may be +consumed by feature command atoms or deeper workflow operation modules. A +backend without mutations does not receive an empty operation capability for +symmetry. + +`ApiTransportService` remains private transport infrastructure that constructs +generated clients and applies common HTTP configuration. Generated clients and +the transport are not imported by resources or features. The former broad +`YieldApiService`, `LegacyApiService`, and `BorrowApiService` contracts are +replaced by Yield, Legacy, and Borrow resource-source and operation +capabilities. Capability implementations perform transport mapping and domain +decoding; Authoritative Resources add caching and reactive lifetimes. + +React hooks and components invoke effects through feature-owned atoms. Network, +persistence, tracking, wallet, polling, and transaction side effects belong in +Effect services and Authoritative Resources rather than UI hooks. Generated +runtime API clients are private to `services/api`; approved `domain/schema` and +`domain/borrow` modules may import generated schema artifacts only. + +## Application runtime + +`src/app/runtime/application-router-runtime.ts` is a synchronous base runtime +that constructs the scoped `ApplicationRouter` around the memory router. +`app-runtime.ts` consumes that router context and composes bootstrap +configuration, focused Yield, Legacy, and Borrow capability ports, rich errors, +persistence, tracking, `WidgetNavigation`, and wallet-modal commands. The +derived `wallet-runtime.ts` receives the application context and adds its scoped +wallet and transaction-workflow services. +Borrow configuration is optional at construction time; invoking an unavailable +borrow capability produces the typed error. + +All application atoms resolve dependencies through `appRuntime`. Feature-local +atoms may own synchronous state directly, but must not construct another +runtime or hide an independent service graph. The Application Router base +runtime is the only lower-level exception and contains no feature services. +Mounting a widget creates a new registry and lifecycle-sensitive service state; +remounting therefore starts cleanly. + +`ApplicationRouter` owns one memory router for an Application Runtime +Generation and disposes it with that generation. The root route configuration is +assembled at the top-level React composition seam in `App.tsx` and seeded into +the registry when it is created, so runtime construction does not import React +composition. React synchronously reads the router from an internal Atom only to +pass it to `RouterProvider`. +`WidgetNavigation` is constructed directly from `ApplicationRouter` and is the +headless application-runtime command interface. Application-owned navigation +uses canonical absolute paths from commands and workflow transition events; +derived view Atoms do not publish navigation outcomes for React to apply. +Declarative route guards and view-local navigation remain React concerns. + +## Effect Atom state conventions + +Effect atoms own application configuration, asynchronous resources, workflow +state, mutations, and cross-feature read models. Resource keys must describe +their complete input, failures remain typed, and mutation success refreshes +only declared dependent resources. React hooks should be thin adapters over +atoms or derived read models, not alternate state owners. + +An Authoritative Resource is the sole owner of one cacheable canonical remote +fact. Its interface accepts complete explicit identity and never reads current, +selected, or visible feature state. It caches decoded domain-facing facts +rather than generated transport DTOs or feature-shaped read models. Features +bind current Wallet Scope and other contextual inputs, then derive eligibility, +selection, summaries, and UI state from the resource result. + +Equivalent semantic requests use one canonical Atom identity. Cross-request +entity normalization is opt-in per resource and is introduced only when the +resource proves that response shapes, completeness, freshness, and missing-item +semantics are compatible. Resource state, including normalized entities, is +scoped to one Widget Instance's Atom registry and is never stored in a +module-global cache or independent runtime. + +Each resource module owns its stale and idle policy, retry behavior, polling, +request concurrency, pagination, partial-response policy, typed failures, and +stale-result behavior. Callers may observe, load more through a semantic pull +interface, request explicit retry or refresh, and derive new Atoms; they do not +choose offsets, page sizes, cache policy, or retry schedules. + +Commands publish semantic invalidation keys for changed remote facts. Resource +modules subscribe by their explicit identity, so one invalidation refreshes all +affected cached queries without commands importing concrete Atom families. +Direct refresh is reserved for explicit retry or user refresh. + +Migration to Authoritative Resources proceeds as completed vertical slices. +Each slice introduces its capability port and resource, migrates every caller, +replaces feature-specific projections, adds interface-level tests, and removes +the previous atoms and duplicate client methods before the next slice begins. +Existing direct feature reads are migration debt and must not be copied into new +or materially refactored code. + +Feature facades expose stable read-only view Atoms and writable command Atoms. +They retain mutable state, dynamic resource Atom identities, retry targets, and +pagination implementations privately. Published view values contain neither +nested Atoms or Atom factories nor command callbacks; the facade resolves the +active resource and forwards user intent internally. Deterministic +calculations remain plain TypeScript. + +`features/yield-entry` owns the shared Yield Entry capability used by Earn and +position details: amount constraints, validation, KYC projection, Enter Action +Command preparation, submission decisions, and their command effects. +`features/yield-summary` owns shared read-only yield projections such as +provider details, reward-token details, and semantic yield type. Earn, +position details, transaction flows, activity, and portfolio consume these +modules through narrow public entries rather than importing implementation +from one another. + +Use React Context only when the value is inherently tree-scoped, such as a +compound component, host DOM element, router-rendered application route +content, or required third-party provider. Do not introduce new page or +application-state contexts. + +## Supported lifecycle + +The package supports one active StakeKit widget instance per document. +Mounting multiple widgets concurrently on the same page is unsupported. A +single widget may be unmounted and mounted again; registry-owned workflow and +lifecycle state is recreated for that new mount. + +## React Context policy + +Effect atoms own widget configuration, feature workflow state, shared read +models, and application lifecycle state. React Context is reserved for values +whose meaning is the React subtree itself or for libraries that require their +own provider. + +The remaining widget-owned contexts are intentional: + +- `ApplicationRouteContentContext` supplies the application subtree to the + static data-router root route without making the route definition depend on + application providers. +- `CollapsibleContext`, `CopyTextContext`, `SelectModalContext`, and the amount + toggle context are private compound-component contracts. Their state belongs + to one component subtree. +- `BackButtonContext` is a compound layout override that marks the dashboard + subtree in which a back button is rendered. +- `CurrentLayoutContext` coordinates measurements between the active routed + page and its surrounding animated layout. +- `SKLocationContext` adapts the current and previous React Router locations + for the routed subtree. +- `RootElementContext` exposes the widget host element to portal and overlay + descendants in that host subtree. + +Effect Atom's registry context and the contexts supplied by i18next, TanStack +Query, Wagmi, RainbowKit, and the Solana wallet adapters are third-party runtime +contracts and remain at application composition boundaries. + +RainbowKit exposes modal commands only through React Context. One named +provider adapter installs connect- and chain-modal commands into the +runtime-scoped `WalletModal` interface and releases them with the provider +lifetime. Feature state never stores or transports those callbacks, and no +module-global latest-callback holder is used. + +Page workflows must not introduce React contexts. Earn, position details, +activity, completion, transaction flow, tracking, summary, configuration, and +mount-animation state are registry-scoped atoms or models derived from atoms. + +## Transaction flows + +Transaction execution is split by ownership: + +- `features/classic-transaction-flow` owns the Classic Review, Steps, and + Complete journey and its Flow Session. +- `features/borrow-transaction-flow` owns the Borrow Review, Steps, and Complete + journey and its Flow Session. `features/borrow` starts it through immutable + intake and observes its read-only lifecycle outcomes; the flow never imports + the Borrow feature. +- `features/transaction-workflow` owns one fresh scoped execution machine per + immutable Transaction Workflow Input. Its scoped Atom is the sole lifecycle + owner; returned read capabilities are passive and retained commands cannot + revive the machine after that scope exits. + +The shared Transaction Workflow contains execution mechanics only. Journey +projection, navigation decisions, handoff cleanup, and completion behavior +remain in the Classic and Borrow modules; application-owned destinations are +executed through the application-runtime `WidgetNavigation` module. diff --git a/packages/widget/package.json b/packages/widget/package.json index 1e133d03e..b2097b38d 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -43,9 +43,17 @@ "scripts": { "dev": "vite -c vite/vite.config.dev.ts", "start": "pnpm dev", - "build": "pnpm lint && pnpm clean && pnpm build:package && pnpm build:bundle && pnpm build:website && pnpm build:types", + "build": "pnpm clean && pnpm build:package && pnpm build:bundle && pnpm build:website && pnpm build:types", "lint": "biome check . && tsc", - "test": "vitest -c vite/vite.config.dev.ts --retry 2 run", + "test": "pnpm test:unit && pnpm test:dom && pnpm test:browser", + "test:unit": "vitest -c vite/vitest.config.ts --project unit run", + "test:unit:watch": "vitest -c vite/vitest.config.ts --project unit", + "test:dom": "vitest -c vite/vitest.config.ts --project dom run", + "test:dom:watch": "vitest -c vite/vitest.config.ts --project dom", + "test:browser": "vitest -c vite/vitest.config.ts --project browser run", + "test:changed": "vitest -c vite/vitest.config.ts run --project unit --project dom --changed", + "test:changed:all": "vitest -c vite/vitest.config.ts run --changed", + "test:changed:unit": "vitest -c vite/vitest.config.ts run --project unit --changed", "format": "biome check --write . --unsafe", "build:website": "vite -c vite/vite.config.website build", "build:package": "vite -c vite/vite.config.package.ts build", @@ -54,7 +62,7 @@ "build:types": "tsc --project tsconfig.build.json", "clean": "rm -rf dist", "preview": "vite -c vite/vite.config.dev.ts preview --outDir dist/website", - "gen:api": "tsx scripts/generate-effect-openapi.ts" + "gen:api": "node --import tsx scripts/generate-effect-openapi.ts" }, "peerDependencies": { "react": ">=18", @@ -75,7 +83,9 @@ "@cosmos-kit/keplr": "catalog:", "@cosmos-kit/leap": "catalog:", "@cosmos-kit/walletconnect": "catalog:", + "@effect/atom-react": "catalog:", "@effect/openapi-generator": "catalog:", + "@effect/platform-browser": "catalog:", "@effect/platform-node": "catalog:", "@faker-js/faker": "catalog:", "@ledgerhq/wallet-api-client": "catalog:", @@ -91,9 +101,10 @@ "@rolldown/plugin-babel": "catalog:", "@safe-global/safe-apps-provider": "catalog:", "@safe-global/safe-apps-sdk": "catalog:", + "@solana-mobile/wallet-adapter-mobile": "catalog:", "@solana/wallet-adapter-base": "catalog:", - "@solana/wallet-adapter-react": "catalog:", "@solana/wallet-adapter-wallets": "catalog:", + "@solana/wallet-standard-wallet-adapter-base": "catalog:", "@solana/web3.js": "catalog:", "@stakekit/rainbowkit": "catalog:", "@tanstack/react-query": "catalog:", @@ -109,6 +120,7 @@ "@types/lodash.uniqwith": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", + "@typescript/native": "catalog:", "@vanilla-extract/css": "catalog:", "@vanilla-extract/dynamic": "catalog:", "@vanilla-extract/recipes": "catalog:", @@ -116,19 +128,19 @@ "@vanilla-extract/vite-plugin": "catalog:", "@vitejs/plugin-react": "catalog:", "@vitest/browser-playwright": "catalog:", - "@xstate/react": "catalog:", - "@xstate/store": "catalog:", + "@wallet-standard/app": "catalog:", + "@wallet-standard/base": "catalog:", "autoprefixer": "catalog:", "babel-plugin-react-compiler": "catalog:", "bignumber.js": "catalog:", "chain-registry": "catalog:", "clsx": "catalog:", "cosmjs-types": "catalog:", - "date-fns": "catalog:", "effect": "catalog:", "eventemitter3": "catalog:", "i18next": "catalog:", "i18next-browser-languagedetector": "catalog:", + "jsdom": "catalog:", "lodash.merge": "catalog:", "lodash.uniqwith": "catalog:", "mipd": "catalog:", @@ -137,14 +149,11 @@ "msw": "catalog:", "playwright": "catalog:", "postcss": "catalog:", - "purify-ts": "catalog:", "react": "catalog:", "react-dom": "catalog:", "react-i18next": "catalog:", "react-loading-skeleton": "catalog:", "react-router": "catalog:", - "reselect": "catalog:", - "rxjs": "catalog:", "swagger2openapi": "catalog:", "tsx": "catalog:", "typescript": "catalog:", @@ -155,7 +164,6 @@ "vitest": "catalog:", "vitest-browser-react": "catalog:", "wagmi": "catalog:", - "xstate": "catalog:", "yaml": "catalog:", "recharts": "catalog:" }, diff --git a/packages/widget/public/mockServiceWorker.js b/packages/widget/public/mockServiceWorker.js index 57451a301..0c970efc9 100644 --- a/packages/widget/public/mockServiceWorker.js +++ b/packages/widget/public/mockServiceWorker.js @@ -7,114 +7,114 @@ * - Please do NOT modify this file. */ -const PACKAGE_VERSION = "2.12.2"; -const INTEGRITY_CHECKSUM = "4db4a41e972cec1b64cc569c66952d82"; -const IS_MOCKED_RESPONSE = Symbol("isMockedResponse"); -const activeClientIds = new Set(); +const PACKAGE_VERSION = '2.15.0' +const INTEGRITY_CHECKSUM = '03cb67ac84128e63d7cd722a6e5b7f1e' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() -addEventListener("install", () => { - self.skipWaiting(); -}); +addEventListener('install', function () { + self.skipWaiting() +}) -addEventListener("activate", (event) => { - event.waitUntil(self.clients.claim()); -}); +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) -addEventListener("message", async (event) => { - const clientId = Reflect.get(event.source || {}, "id"); +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') if (!clientId || !self.clients) { - return; + return } - const client = await self.clients.get(clientId); + const client = await self.clients.get(clientId) if (!client) { - return; + return } const allClients = await self.clients.matchAll({ - type: "window", - }); + type: 'window', + }) switch (event.data) { - case "KEEPALIVE_REQUEST": { + case 'KEEPALIVE_REQUEST': { sendToClient(client, { - type: "KEEPALIVE_RESPONSE", - }); - break; + type: 'KEEPALIVE_RESPONSE', + }) + break } - case "INTEGRITY_CHECK_REQUEST": { + case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { - type: "INTEGRITY_CHECK_RESPONSE", + type: 'INTEGRITY_CHECK_RESPONSE', payload: { packageVersion: PACKAGE_VERSION, checksum: INTEGRITY_CHECKSUM, }, - }); - break; + }) + break } - case "MOCK_ACTIVATE": { - activeClientIds.add(clientId); + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) sendToClient(client, { - type: "MOCKING_ENABLED", + type: 'MOCKING_ENABLED', payload: { client: { id: client.id, frameType: client.frameType, }, }, - }); - break; + }) + break } - case "CLIENT_CLOSED": { - activeClientIds.delete(clientId); + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) const remainingClients = allClients.filter((client) => { - return client.id !== clientId; - }); + return client.id !== clientId + }) // Unregister itself when there are no more clients if (remainingClients.length === 0) { - self.registration.unregister(); + self.registration.unregister() } - break; + break } } -}); +}) -addEventListener("fetch", (event) => { - const requestInterceptedAt = Date.now(); +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() // Bypass navigation requests. - if (event.request.mode === "navigate") { - return; + if (event.request.mode === 'navigate') { + return } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. if ( - event.request.cache === "only-if-cached" && - event.request.mode !== "same-origin" + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' ) { - return; + return } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests // after it's been terminated (still remains active until the next reload). if (activeClientIds.size === 0) { - return; + return } - const requestId = crypto.randomUUID(); - event.respondWith(handleRequest(event, requestId, requestInterceptedAt)); -}); + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) +}) /** * @param {FetchEvent} event @@ -122,28 +122,38 @@ addEventListener("fetch", (event) => { * @param {number} requestInterceptedAt */ async function handleRequest(event, requestId, requestInterceptedAt) { - const client = await resolveMainClient(event); - const requestCloneForEvents = event.request.clone(); + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() const response = await getResponse( event, client, requestId, requestInterceptedAt, - ); + ) // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents); + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Omit the body of server-sent event stream responses. + // Cloning such responses would prevent client-side stream cancelations + // from reaching the original stream (a teed stream only cancels its + // source once both of its branches cancel) and would buffer the + // entire stream into the unconsumed clone indefinitely. + const isEventStreamResponse = response.headers + .get('content-type') + ?.toLowerCase() + .startsWith('text/event-stream') // Clone the response so both the client and the library could consume it. - const responseClone = response.clone(); + const responseClone = isEventStreamResponse ? null : response.clone() sendToClient( client, { - type: "RESPONSE", + type: 'RESPONSE', payload: { isMockedResponse: IS_MOCKED_RESPONSE in response, request: { @@ -151,19 +161,21 @@ async function handleRequest(event, requestId, requestInterceptedAt) { ...serializedRequest, }, response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, + type: response.type, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + body: responseClone ? responseClone.body : null, }, }, }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ); + responseClone && responseClone.body + ? [serializedRequest.body, responseClone.body] + : [], + ) } - return response; + return response } /** @@ -175,30 +187,30 @@ async function handleRequest(event, requestId, requestInterceptedAt) { * @returns {Promise} */ async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId); + const client = await self.clients.get(event.clientId) if (activeClientIds.has(event.clientId)) { - return client; + return client } - if (client?.frameType === "top-level") { - return client; + if (client?.frameType === 'top-level') { + return client } const allClients = await self.clients.matchAll({ - type: "window", - }); + type: 'window', + }) return allClients .filter((client) => { // Get only those clients that are currently visible. - return client.visibilityState === "visible"; + return client.visibilityState === 'visible' }) .find((client) => { // Find the client ID that's recorded in the // set of clients that have registered the worker. - return activeClientIds.has(client.id); - }); + return activeClientIds.has(client.id) + }) } /** @@ -211,36 +223,36 @@ async function resolveMainClient(event) { async function getResponse(event, client, requestId, requestInterceptedAt) { // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone(); + const requestClone = event.request.clone() function passthrough() { // Cast the request headers to a new Headers instance // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers); + const headers = new Headers(requestClone.headers) // Remove the "accept" header value that marked this request as passthrough. // This prevents request alteration and also keeps it compliant with the // user-defined CORS policies. - const acceptHeader = headers.get("accept"); + const acceptHeader = headers.get('accept') if (acceptHeader) { - const values = acceptHeader.split(",").map((value) => value.trim()); + const values = acceptHeader.split(',').map((value) => value.trim()) const filteredValues = values.filter( - (value) => value !== "msw/passthrough", - ); + (value) => value !== 'msw/passthrough', + ) if (filteredValues.length > 0) { - headers.set("accept", filteredValues.join(", ")); + headers.set('accept', filteredValues.join(', ')) } else { - headers.delete("accept"); + headers.delete('accept') } } - return fetch(requestClone, { headers }); + return fetch(requestClone, { headers }) } // Bypass mocking when the client is not active. if (!client) { - return passthrough(); + return passthrough() } // Bypass initial page load requests (i.e. static assets). @@ -248,15 +260,15 @@ async function getResponse(event, client, requestId, requestInterceptedAt) { // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // and is not ready to handle requests. if (!activeClientIds.has(client.id)) { - return passthrough(); + return passthrough() } // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request); + const serializedRequest = await serializeRequest(event.request) const clientMessage = await sendToClient( client, { - type: "REQUEST", + type: 'REQUEST', payload: { id: requestId, interceptedAt: requestInterceptedAt, @@ -264,19 +276,19 @@ async function getResponse(event, client, requestId, requestInterceptedAt) { }, }, [serializedRequest.body], - ); + ) switch (clientMessage.type) { - case "MOCK_RESPONSE": { - return respondWithMock(clientMessage.data); + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) } - case "PASSTHROUGH": { - return passthrough(); + case 'PASSTHROUGH': { + return passthrough() } } - return passthrough(); + return passthrough() } /** @@ -287,21 +299,21 @@ async function getResponse(event, client, requestId, requestInterceptedAt) { */ function sendToClient(client, message, transferrables = []) { return new Promise((resolve, reject) => { - const channel = new MessageChannel(); + const channel = new MessageChannel() channel.port1.onmessage = (event) => { - if (event.data?.error) { - return reject(event.data.error); + if (event.data && event.data.error) { + return reject(event.data.error) } - resolve(event.data); - }; + resolve(event.data) + } client.postMessage(message, [ channel.port2, ...transferrables.filter(Boolean), - ]); - }); + ]) + }) } /** @@ -314,17 +326,17 @@ function respondWithMock(response) { // instance will have status code set to 0. Since it's not possible to create // a Response instance with status code 0, handle that use-case separately. if (response.status === 0) { - return Response.error(); + return Response.error() } - const mockedResponse = new Response(response.body, response); + const mockedResponse = new Response(response.body, response) Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { value: true, enumerable: true, - }); + }) - return mockedResponse; + return mockedResponse } /** @@ -345,5 +357,5 @@ async function serializeRequest(request) { referrerPolicy: request.referrerPolicy, body: await request.arrayBuffer(), keepalive: request.keepalive, - }; + } } diff --git a/packages/widget/scripts/generate-effect-openapi.ts b/packages/widget/scripts/generate-effect-openapi.ts index 0512244ac..38edc52a2 100644 --- a/packages/widget/scripts/generate-effect-openapi.ts +++ b/packages/widget/scripts/generate-effect-openapi.ts @@ -10,16 +10,50 @@ type JsonPatchOperation = { value: unknown; }; +const nullableScalarPatch = ({ + description, + example, + property, + schema, + type, +}: { + description: string; + example: unknown; + property: string; + schema: string; + type: "number" | "string"; +}): JsonPatchOperation => ({ + op: "replace", + path: `/components/schemas/${schema}/properties/${property}`, + value: { description, example, nullable: true, type }, +}); + +type SpecOutputConfig = { + format: "httpclient" | "httpclient-type-only"; + outputPath: string; + schemaOnly?: boolean; +}; + type SpecConfig = { name: string; - outputPath: string; + outputs: ReadonlyArray; patches: JsonPatchOperation[]; + prepareSpec?: (contents: string) => string; specFileName: string; url: string; }; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const widgetRoot = path.resolve(scriptDir, ".."); +const workspaceRoot = path.resolve(widgetRoot, "../.."); +const binaryPath = (root: string, name: string) => + path.join( + root, + "node_modules/.bin", + `${name}${process.platform === "win32" ? ".CMD" : ""}` + ); +const openApiGeneratorBin = binaryPath(widgetRoot, "openapigen"); +const biomeBin = binaryPath(workspaceRoot, "biome"); const generatedFileHeader = [ "// @ts-nocheck", "// biome-ignore-all lint: generated by Effect OpenAPI", @@ -31,7 +65,17 @@ const specs: SpecConfig[] = [ name: "LegacyApi", url: process.env.LEGACY_API_SPEC_URL ?? "https://api.stakek.it/docs.yaml", specFileName: "legacy-api.yaml", - outputPath: path.join(widgetRoot, "src/generated/api/legacy.ts"), + outputs: [ + { + format: "httpclient-type-only", + outputPath: path.join(widgetRoot, "src/generated/api/legacy.ts"), + }, + { + format: "httpclient", + outputPath: path.join(widgetRoot, "src/generated/api/legacy-schema.ts"), + schemaOnly: true, + }, + ], patches: [ // Upstream currently declares regionCode as an object, but production // payloads and widget geo-block handling use it as a string. @@ -47,8 +91,245 @@ const specs: SpecConfig[] = [ url: process.env.YIELD_API_SPEC_URL ?? "https://api.stg.yield.xyz/docs.yaml", specFileName: "yield-api.yaml", - outputPath: path.join(widgetRoot, "src/generated/api/yield.ts"), + outputs: [ + { + format: "httpclient-type-only", + outputPath: path.join(widgetRoot, "src/generated/api/yield.ts"), + }, + { + format: "httpclient", + outputPath: path.join(widgetRoot, "src/generated/api/yield-schema.ts"), + schemaOnly: true, + }, + ], patches: [ + // These DTO properties have concrete scalar types in the Yield API + // source, but their Swagger decorators omit the explicit property type. + // Patch the spec so openapigen does not emit `{}` placeholders. + nullableScalarPatch({ + schema: "LiquidityStateDto", + property: "liquidity", + type: "string", + description: "Available liquidity in underlying token units", + example: "250000.00", + }), + nullableScalarPatch({ + schema: "LiquidityStateDto", + property: "utilization", + type: "string", + description: "Utilization rate as a decimal (e.g., 0.8 = 80%)", + example: "0.80", + }), + nullableScalarPatch({ + schema: "YieldFeeConfigurationDto", + property: "managementFeeBps", + type: "number", + description: "Management fee in basis points", + example: 100, + }), + nullableScalarPatch({ + schema: "YieldFeeConfigurationDto", + property: "performanceFeeBps", + type: "number", + description: "Performance fee in basis points", + example: 1000, + }), + nullableScalarPatch({ + schema: "YieldFeeConfigurationDto", + property: "depositFeeBps", + type: "number", + description: "Deposit fee in basis points", + example: 0, + }), + nullableScalarPatch({ + schema: "YieldFeeConfigurationDto", + property: "allocatorVaultContractAddress", + type: "string", + description: "Partner allocator vault contract address", + example: "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b", + }), + nullableScalarPatch({ + schema: "ProviderDto", + property: "tvlUsd", + type: "string", + description: "Total TVL across the entire provider in USD", + example: "10,200,000", + }), + nullableScalarPatch({ + schema: "ValidatorProviderDto", + property: "tvlUsd", + type: "string", + description: "Total TVL across the entire provider in USD", + example: "10,200,000", + }), + nullableScalarPatch({ + schema: "CuratorDto", + property: "name", + type: "string", + description: "Curator name", + example: "Steakhouse Financial", + }), + nullableScalarPatch({ + schema: "CuratorDto", + property: "description", + type: "string", + description: "Curator description", + example: "Vault curator", + }), + nullableScalarPatch({ + schema: "CuratorDto", + property: "logoURI", + type: "string", + description: "Curator logo URI", + example: "https://example.com/curator.svg", + }), + nullableScalarPatch({ + schema: "YieldRiskCredoraDto", + property: "rating", + type: "string", + description: "Credora rating", + example: "A", + }), + nullableScalarPatch({ + schema: "YieldRiskCredoraDto", + property: "score", + type: "number", + description: "Credora score (1-5)", + example: 4.5, + }), + nullableScalarPatch({ + schema: "YieldRiskCredoraDto", + property: "psl", + type: "number", + description: "Probability of Significant Loss (annualized)", + example: 0.01, + }), + nullableScalarPatch({ + schema: "YieldRiskCredoraDto", + property: "publishDate", + type: "string", + description: "Credora publish date", + example: "2026-01-01", + }), + nullableScalarPatch({ + schema: "YieldRiskCredoraDto", + property: "curator", + type: "string", + description: "Credora curator name", + example: "Credora", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsMetricsDto", + property: "users", + type: "number", + description: "Users count from Staking Rewards risk metrics", + example: 1000, + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "rating", + type: "string", + description: "Staking Rewards rating", + example: "A", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "score", + type: "number", + description: "Staking Rewards score (1-5)", + example: 4, + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "potentialRating", + type: "string", + description: "Staking Rewards potential rating", + example: "A+", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "potentialScore", + type: "number", + description: "Staking Rewards potential score (1-5)", + example: 4.5, + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "ratedAt", + type: "string", + description: "Date when rating was assessed", + example: "2026-01-01", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "ratedSince", + type: "string", + description: "Date since product has been rated", + example: "2025-01-01", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "profileUrl", + type: "string", + description: "Staking Rewards product profile URL", + example: "https://stakingrewards.com/provider", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "reportUrl", + type: "string", + description: "Staking Rewards full report URL", + example: "https://stakingrewards.com/report", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "providerName", + type: "string", + description: "Staking Rewards provider name", + example: "Provider", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "version", + type: "string", + description: "Staking Rewards methodology version", + example: "v1", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "type", + type: "string", + description: "Staking Rewards product type", + example: "validator", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "chain", + type: "string", + description: "Chain label returned by Staking Rewards", + example: "ethereum", + }), + nullableScalarPatch({ + schema: "YieldRiskStakingRewardsDto", + property: "contractAddress", + type: "string", + description: "Contract address returned by Staking Rewards", + example: "0x0000000000000000000000000000000000000001", + }), + { + op: "replace", + path: "/components/schemas/BalanceDto/properties/priceRange", + value: { + type: "object", + description: + "Price range for concentrated liquidity positions in tokens[1]/tokens[0] format", + properties: { + min: { type: "string" }, + max: { type: "string" }, + }, + required: ["min", "max"], + }, + }, // The spec marks these date-time fields as nullable, but openapigen beta // currently drops null for nullable date-time strings. Replacing the // property without format preserves the production response shape. @@ -72,6 +353,29 @@ const specs: SpecConfig[] = [ }, ], }, + { + name: "BorrowApi", + url: + process.env.BORROW_API_SPEC_URL ?? "https://borrow.yield.xyz/docs.json", + specFileName: "borrow-api.json", + outputs: [ + { + format: "httpclient-type-only", + outputPath: path.join(widgetRoot, "src/generated/api/borrow-client.ts"), + }, + { + format: "httpclient", + outputPath: path.join(widgetRoot, "src/generated/api/borrow.ts"), + schemaOnly: true, + }, + ], + patches: [], + prepareSpec: (contents) => + contents.replaceAll( + '"allOf":[{"$ref":"#/components/schemas/ArgumentSchemaPropertyDto"}]', + '"type":"object"' + ), + }, ]; const run = async ( @@ -135,55 +439,102 @@ const fetchSpec = async (spec: SpecConfig) => { return response.text(); }; +const extractSchemaOnlyOutput = (output: string, specName: string) => { + const clientMarker = "\nexport interface OperationConfig {"; + const clientStart = output.indexOf(clientMarker); + + if (clientStart === -1) { + throw new Error( + `${specName} schema generation did not contain the expected client marker` + ); + } + + return `${output + .slice(0, clientStart) + .replace(/^import \* as Data from "effect\/Data";?\r?\n/m, "") + .replace(/^import \* as Effect from "effect\/Effect";?\r?\n/m, "") + .replace(/^import type \{ SchemaError \} from "effect\/Schema";?\r?\n/m, "") + .replace( + /^import(?: type)? \* as HttpClient[^\n]+"effect\/unstable\/http\/[^\n]+\r?\n/gm, + "" + ) + .trimEnd()}\n`; +}; + const generateSpec = async (spec: SpecConfig, tempDir: string) => { const specPath = path.join(tempDir, spec.specFileName); const patchPath = path.join(tempDir, `${spec.specFileName}.patch.json`); console.log(`Fetching ${spec.name} spec from ${spec.url}`); - await writeFile(specPath, await fetchSpec(spec)); + const specContents = await fetchSpec(spec); + await writeFile(specPath, spec.prepareSpec?.(specContents) ?? specContents); await writeFile(patchPath, `${JSON.stringify(spec.patches, null, 2)}\n`); - await mkdir(path.dirname(spec.outputPath), { recursive: true }); - - console.log(`Generating ${path.relative(widgetRoot, spec.outputPath)}`); - const { stdout, stderr } = await run("pnpm", [ - "exec", - "openapigen", - "--spec", - specPath, - "--name", - spec.name, - "--format", - "httpclient-type-only", - "--patch", - patchPath, - ]); - - if (stderr.trim()) { - console.warn(stderr.trim()); - } - if (!stdout.trim()) { - throw new Error(`${spec.name} generation produced empty output`); - } + for (const output of spec.outputs) { + await mkdir(path.dirname(output.outputPath), { recursive: true }); + + console.log( + `Generating ${path.relative(widgetRoot, output.outputPath)} (${output.format})` + ); + const { stdout, stderr } = await run(openApiGeneratorBin, [ + "--spec", + specPath, + "--name", + spec.name, + "--format", + output.format, + "--patch", + patchPath, + ]); - await writeFile(spec.outputPath, `${generatedFileHeader}${stdout}`); + if (stderr.trim()) { + console.warn(stderr.trim()); + } + + if (!stdout.trim()) { + throw new Error( + `${spec.name} ${output.format} generation produced empty output` + ); + } + + const generatedOutput = output.schemaOnly + ? extractSchemaOnlyOutput(stdout, spec.name) + : stdout; + + await writeFile( + output.outputPath, + `${generatedFileHeader}${generatedOutput}` + ); + } }; const main = async () => { const tempDir = await mkdtemp(path.join(tmpdir(), "stakekit-openapi-")); + const requestedSpecs = new Set(process.argv.slice(2)); + const selectedSpecs = requestedSpecs.size + ? specs.filter((spec) => requestedSpecs.has(spec.name)) + : specs; + + if (!selectedSpecs.length) { + throw new Error( + `No OpenAPI specs matched: ${[...requestedSpecs].join(", ")}` + ); + } try { - for (const spec of specs) { + for (const spec of selectedSpecs) { await generateSpec(spec, tempDir); } - await run("pnpm", [ - "exec", - "biome", + await run(biomeBin, [ "check", "--write", "--unsafe", - ...specs.map((spec) => path.relative(widgetRoot, spec.outputPath)), + ...selectedSpecs.flatMap((spec) => + spec.outputs.map((output) => + path.relative(widgetRoot, output.outputPath) + ) + ), ]); } finally { await rm(tempDir, { force: true, recursive: true }); diff --git a/packages/widget/scripts/prepare-canary-release.test.ts b/packages/widget/scripts/prepare-canary-release.test.ts new file mode 100644 index 000000000..c018ccc53 --- /dev/null +++ b/packages/widget/scripts/prepare-canary-release.test.ts @@ -0,0 +1,80 @@ +import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { + deriveCanaryVersion, + prepareCanaryRelease, +} from "./prepare-canary-release"; + +describe("deriveCanaryVersion", () => { + it("derives a patch canary from the current stable release", () => { + expect( + deriveCanaryVersion({ + currentVersion: "0.0.282", + latestVersion: "0.0.282", + runNumber: "418", + }) + ).toBe("0.0.283-canary.418"); + }); + + it("rejects a stale selected branch", () => { + expect(() => + deriveCanaryVersion({ + currentVersion: "0.0.281", + latestVersion: "0.0.282", + runNumber: "420", + }) + ).toThrow(/Rebase or update the branch/); + }); + + it("rejects a prerelease branch version", () => { + expect(() => + deriveCanaryVersion({ + currentVersion: "0.0.283-canary.1", + latestVersion: "0.0.282", + runNumber: "421", + }) + ).toThrow(/must be a stable semantic version/); + }); + + it("rejects invalid run numbers", () => { + expect(() => + deriveCanaryVersion({ + currentVersion: "0.0.282", + latestVersion: "0.0.282", + runNumber: "0", + }) + ).toThrow(/must be a positive integer/); + }); +}); + +describe("prepareCanaryRelease", () => { + it("updates only the runner package version and writes action outputs", () => { + const directory = mkdtempSync(join(tmpdir(), "stakekit-canary-")); + const packageJsonPath = join(directory, "package.json"); + const githubOutput = join(directory, "github-output"); + + writeFileSync( + packageJsonPath, + `${JSON.stringify({ name: "@stakekit/widget", version: "0.0.282" })}\n` + ); + writeFileSync(githubOutput, ""); + + expect( + prepareCanaryRelease({ + packageDir: directory, + latestVersion: "0.0.282", + runNumber: "422", + githubOutput, + }) + ).toBe("0.0.283-canary.422"); + const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { + readonly version: string; + }; + expect(packageJson.version).toBe("0.0.283-canary.422"); + expect(readFileSync(githubOutput, "utf8")).toBe( + "base_version=0.0.282\nversion=0.0.283-canary.422\n" + ); + }); +}); diff --git a/packages/widget/scripts/prepare-canary-release.ts b/packages/widget/scripts/prepare-canary-release.ts new file mode 100644 index 000000000..6a8b0ca27 --- /dev/null +++ b/packages/widget/scripts/prepare-canary-release.ts @@ -0,0 +1,139 @@ +import { appendFileSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const stableVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/; +const runNumberPattern = /^[1-9]\d*$/; +const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +type StableVersion = { + readonly major: number; + readonly minor: number; + readonly patch: number; +}; + +type DeriveCanaryVersionOptions = { + readonly currentVersion: string; + readonly latestVersion: string; + readonly runNumber: string; +}; + +type PrepareCanaryReleaseOptions = Omit< + DeriveCanaryVersionOptions, + "currentVersion" +> & { + readonly packageDir: string; + readonly githubOutput: string; +}; + +type PackageJson = Record & { + readonly version?: unknown; +}; + +const parseStableVersion = (version: string, label: string): StableVersion => { + const match = stableVersionPattern.exec(version); + + if (!match) { + throw new Error( + `${label} must be a stable semantic version, got ${version}` + ); + } + + return { + major: Number(match[1]), + minor: Number(match[2]), + patch: Number(match[3]), + }; +}; + +const formatVersion = ({ major, minor, patch }: StableVersion) => + `${major}.${minor}.${patch}`; + +export const deriveCanaryVersion = ({ + currentVersion, + latestVersion, + runNumber, +}: DeriveCanaryVersionOptions): string => { + const current = parseStableVersion(currentVersion, "Package version"); + const latest = parseStableVersion(latestVersion, "npm latest version"); + + if (formatVersion(current) !== formatVersion(latest)) { + throw new Error( + `Selected branch has ${currentVersion}, but npm latest is ${latestVersion}. ` + + "Rebase or update the branch before publishing a canary." + ); + } + + if (!runNumberPattern.test(runNumber)) { + throw new Error( + `GitHub run number must be a positive integer, got ${runNumber}` + ); + } + + const target = { ...current, patch: current.patch + 1 }; + + return `${formatVersion(target)}-canary.${runNumber}`; +}; + +export const prepareCanaryRelease = ({ + packageDir, + latestVersion, + runNumber, + githubOutput, +}: PrepareCanaryReleaseOptions): string => { + const packageJsonPath = resolve(packageDir, "package.json"); + const packageJson = JSON.parse( + readFileSync(packageJsonPath, "utf8") + ) as PackageJson; + const currentVersion = packageJson.version; + + if (typeof currentVersion !== "string") { + throw new Error(`${packageJsonPath} does not contain a string version`); + } + + const version = deriveCanaryVersion({ + currentVersion, + latestVersion, + runNumber, + }); + + const updatedPackageJson = { ...packageJson, version }; + writeFileSync( + packageJsonPath, + `${JSON.stringify(updatedPackageJson, null, 2)}\n` + ); + appendFileSync( + githubOutput, + `base_version=${currentVersion}\nversion=${version}\n` + ); + + return version; +}; + +const main = () => { + const latestVersion = process.env.LATEST_VERSION; + const runNumber = process.env.GITHUB_RUN_NUMBER; + const githubOutput = process.env.GITHUB_OUTPUT; + + if (!latestVersion || !runNumber || !githubOutput) { + throw new Error( + "LATEST_VERSION, GITHUB_RUN_NUMBER, and GITHUB_OUTPUT are required" + ); + } + + const version = prepareCanaryRelease({ + packageDir, + latestVersion, + runNumber, + githubOutput, + }); + + console.log(`Prepared canary version ${version}`); +}; + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + main(); +} diff --git a/packages/widget/src/App.tsx b/packages/widget/src/App.tsx index 8bc4be7c1..ece7afa45 100644 --- a/packages/widget/src/App.tsx +++ b/packages/widget/src/App.tsx @@ -1,29 +1,43 @@ import "@stakekit/rainbowkit/styles.css"; import "./translation"; -import "./utils/extend-purify"; -import "./styles/theme/global.css"; -import type { ComponentProps, RefObject } from "react"; +import "./shared/styles/theme/global.css"; +import { useAtomValue } from "@effect/atom-react"; +import type { ComponentProps } from "react"; import { createRef, useImperativeHandle, useState } from "react"; import ReactDOM from "react-dom/client"; -import { createMemoryRouter, RouterProvider } from "react-router"; -import { preloadImages } from "./assets/images"; -import { Box } from "./components/atoms/box"; -import { Dashboard } from "./Dashboard"; -import { Providers } from "./providers"; -import { SettingsContextProvider, useSettings } from "./providers/settings"; -import type { SettingsProps, VariantProps } from "./providers/settings/types"; -import { appContainer } from "./style.css"; -import { useLoadErrorTranslations } from "./translation"; -import { Widget } from "./Widget"; +import { RouterProvider } from "react-router/dom"; +import { ApplicationRouteContentProvider } from "./app/composition/application-route-content"; +import { Providers } from "./app/composition/providers"; +import { SKAtomRegistryProvider } from "./app/composition/providers/atom-runtime"; +import { normalizeWidgetConfig } from "./app/config/settings"; +import { useWidgetConfig } from "./app/config/use-widget-config"; +import { acquireWidgetInstanceClaim } from "./app/embedding/widget-instance-claim"; +import { WidgetInstanceReactBoundary } from "./app/embedding/widget-instance-react-boundary"; +import { applicationRoutes } from "./app/routes/application-routes"; +import { ClassicRoutes } from "./app/routes/classic-routes"; +import { DashboardRoutes } from "./app/routes/dashboard-routes"; +import { useHandleDeepLinks } from "./app/routes/hooks/use-handle-deep-links"; +import { applicationRouterAtom } from "./app/runtime/application-router-runtime"; +import { useLoadErrorTranslations } from "./app/translation/use-load-error-translations"; +import { appContainer } from "./features/widget-shell/layout.css"; +import type { + BundledSKWidgetProps, + SKAppProps, + VariantProps, +} from "./public-api/types"; +import { isLedgerDappBrowserProvider } from "./services/wallet/browser-environment"; +import { preloadImages } from "./shared/assets/images"; +import { Box } from "./shared/ui/primitives/box"; preloadImages(); const App = () => { useLoadErrorTranslations(); + useHandleDeepLinks(); - const { dashboardVariant } = useSettings(); + const dashboardVariant = useWidgetConfig("dashboardVariant"); - return dashboardVariant ? : ; + return dashboardVariant ? : ; }; const Root = () => ( @@ -32,35 +46,50 @@ const Root = () => ( ); -export type SKAppProps = SettingsProps & (VariantProps | { variant?: never }); - -export const SKApp = (props: SKAppProps) => { - const variantProps: VariantProps = - props.variant === "zerion" - ? { variant: props.variant, chainModal: props.chainModal } - : { variant: props.variant ?? "default" }; - - const [router] = useState(() => - createMemoryRouter([{ path: "*", Component: Root }]) - ); +const SKAppRouter = ({ + dashboardVariant, +}: { + readonly dashboardVariant: boolean; +}) => { + const router = useAtomValue(applicationRouterAtom); return ( - + }> - + ); }; -export type BundledSKWidgetProps = SKAppProps & { - ref?: RefObject<{ rerender: (newProps: BundledSKWidgetProps) => void }>; +const SKAppContent = (props: SKAppProps) => { + const variantProps: VariantProps = + props.variant === "zerion" + ? { variant: props.variant, chainModal: props.chainModal } + : { variant: props.variant ?? "default" }; + + const settings = normalizeWidgetConfig( + { ...props, ...variantProps }, + { isLedgerLive: isLedgerDappBrowserProvider() } + ); + + return ( + + + + ); }; +export const SKApp = (props: SKAppProps) => ( + + + +); + const BundledSKWidget = (_props: BundledSKWidgetProps) => { const [props, setProps] = useState(_props); @@ -68,7 +97,7 @@ const BundledSKWidget = (_props: BundledSKWidgetProps) => { rerender: (newProps: BundledSKWidgetProps) => setProps(newProps), })); - return ; + return ; }; export const renderSKWidget = ({ @@ -79,16 +108,38 @@ export const renderSKWidget = ({ }) => { if (!rest.apiKey) throw new Error("API key is required"); - const root = ReactDOM.createRoot(container); - - const appRef = createRef<{ rerender: () => void }>() as NonNullable< - BundledSKWidgetProps["ref"] - >; - - root.render(); - - return { - rerender: (newProps: SKAppProps) => - appRef.current.rerender({ ...newProps, ref: appRef }), - }; + const releaseClaim = acquireWidgetInstanceClaim( + container.ownerDocument ?? document + ); + let root: ReturnType; + + try { + root = ReactDOM.createRoot(container); + + const appRef = createRef<{ rerender: () => void }>() as NonNullable< + BundledSKWidgetProps["ref"] + >; + + root.render(); + + let unmounted = false; + + return { + rerender: (newProps: SKAppProps) => + appRef.current.rerender({ ...newProps, ref: appRef }), + unmount: () => { + if (unmounted) return; + + unmounted = true; + try { + root.unmount(); + } finally { + releaseClaim(); + } + }, + }; + } catch (error) { + releaseClaim(); + throw error; + } }; diff --git a/packages/widget/src/Dashboard.tsx b/packages/widget/src/Dashboard.tsx deleted file mode 100644 index 91fb2ca07..000000000 --- a/packages/widget/src/Dashboard.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { Route, Routes } from "react-router"; -import { GlobalModals } from "./components/molecules/global-modals"; -import { ConnectedCheck } from "./navigation/cheks/connected-check"; -// import { RewardsTabPage } from "./pages-dashboard/rewards"; -import { PendingCompletePage } from "./pages/complete/pages/pending-complete.page"; -import { StakeCompletePage } from "./pages/complete/pages/stake-complete.page"; -import { UnstakeCompletePage } from "./pages/complete/pages/unstake-complete.page"; -import { EarnPageContextProvider } from "./pages/details/earn-page/state/earn-page-context"; -import { EarnPageStateUsageBoundaryProvider } from "./pages/details/earn-page/state/earn-page-state-context"; -import { StakeReviewPage } from "./pages/review"; -import { PendingReviewPage } from "./pages/review/pages/pending-review.page"; -import { UnstakeReviewPage } from "./pages/review/pages/unstake-review.page"; -import { StakeStepsPage } from "./pages/steps"; -import { ActivityStepsPage } from "./pages/steps/pages/activity-steps.page"; -import { PendingStepsPage } from "./pages/steps/pages/pending-steps.page"; -import { UnstakeStepsPage } from "./pages/steps/pages/unstake-steps.page"; -import { ActivityTabPage } from "./pages-dashboard/activity"; -import { ActivityDetailsPage } from "./pages-dashboard/activity/activity-details.page"; -import { DashboardWrapper } from "./pages-dashboard/common/components/wrapper"; -import { OverviewPage } from "./pages-dashboard/overview"; -import { EarnPageContent } from "./pages-dashboard/overview/earn-page"; -import { ManagePage } from "./pages-dashboard/overview/manage.page"; -import { PositionDetailsPage } from "./pages-dashboard/position-details"; -import { PositionDetailsActions } from "./pages-dashboard/position-details/components/position-details-actions"; -import { PositionDetailsStakeActions } from "./pages-dashboard/position-details/components/position-details-stake-actions"; -import { DashboardProvider } from "./pages-dashboard/providers/dashboard-context"; -import { useSKLocation } from "./providers/location"; - -const positionDetailsStakeFooterPath = - /^\/positions\/[^/]+\/[^/]+(?:\/stake)?$/; - -export const shouldRegisterDashboardEarnFooterButton = (pathname: string) => - pathname === "/" || positionDetailsStakeFooterPath.test(pathname); - -export const Dashboard = () => { - const { current } = useSKLocation(); - const registerEarnFooterButton = shouldRegisterDashboardEarnFooterButton( - current.pathname - ); - - return ( - - - - - }> - {/* Earn Tab */} - }> - } /> - - }> - } /> - } /> - } /> - - - - {/* Manage Tab */} - } /> - - {/* Position Details */} - } - > - } /> - - {/* Staking */} - - } /> - } /> - } /> - } /> - - - } - /> - - {/* Unstaking */} - - } /> - } /> - } /> - } /> - - - {/* Pending Actions */} - - } /> - } /> - } /> - - - - {/* Rewards Tab */} - {/* } /> */} - - {/* Activity Tab */} - }> - } /> - } - /> - - - - - - - - - ); -}; diff --git a/packages/widget/src/Widget.tsx b/packages/widget/src/Widget.tsx deleted file mode 100644 index 00dfc49a0..000000000 --- a/packages/widget/src/Widget.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { AnimatePresence, LayoutGroup, motion } from "motion/react"; -import { useEffect } from "react"; -import { Navigate, Route, Routes, useNavigate } from "react-router"; -import { GlobalModals } from "./components/molecules/global-modals"; -import { Header } from "./components/molecules/header"; -import { useDetailsMatch } from "./hooks/navigation/use-details-match"; -import { useHandleDeepLinks } from "./hooks/use-handle-deep-links"; -import { useInitParams } from "./hooks/use-init-params"; -import { usePrevious } from "./hooks/use-previous"; -import { useSavedRef } from "./hooks/use-saved-ref"; -import { useUnderMaintenance } from "./hooks/use-under-maintenance"; -import { ConnectedCheck } from "./navigation/cheks/connected-check"; -import { AnimationLayout } from "./navigation/containers/animation-layout"; -import { ActivityCompletePage } from "./pages/complete/pages/activity-complete.page"; -import { PendingCompletePage } from "./pages/complete/pages/pending-complete.page"; -import { StakeCompletePage } from "./pages/complete/pages/stake-complete.page"; -import { UnstakeCompletePage } from "./pages/complete/pages/unstake-complete.page"; -import { Layout } from "./pages/components/layout"; -import { headerContainer } from "./pages/components/layout/styles.css"; -import { PoweredBy } from "./pages/components/powered-by"; -import UnderMaintenance from "./pages/components/under-maintenance"; -import { AnimatedActivityPage } from "./pages/details/activity-page/activity.page"; -import { Details } from "./pages/details/details-page/details.page"; -import { AnimatedEarnPage } from "./pages/details/earn-page/earn.page"; -import { AnimatedPositionsPage } from "./pages/details/positions-page/positions.page"; -import { PositionDetailsPage } from "./pages/position-details"; -import { StakeReviewPage } from "./pages/review"; -import { ActionReviewPage } from "./pages/review/pages/action-review.page"; -import { PendingReviewPage } from "./pages/review/pages/pending-review.page"; -import { UnstakeReviewPage } from "./pages/review/pages/unstake-review.page"; -import { StakeStepsPage } from "./pages/steps"; -import { ActivityStepsPage } from "./pages/steps/pages/activity-steps.page"; -import { PendingStepsPage } from "./pages/steps/pages/pending-steps.page"; -import { UnstakeStepsPage } from "./pages/steps/pages/unstake-steps.page"; -import { useSKLocation } from "./providers/location"; -import { useSKWallet } from "./providers/sk-wallet"; -import { container } from "./style.css"; -import { MaybeWindow } from "./utils/maybe-window"; - -export const Widget = () => { - const underMaintenance = useUnderMaintenance(); - - const { chain, address } = useSKWallet(); - - const prevChain = usePrevious(chain); - const prevAddress = usePrevious(address); - - const { current } = useSKLocation(); - - const pathnameRef = useSavedRef(current.pathname); - const navigateRef = useSavedRef(useNavigate()); - - /** - * On chain change, navigate to home page - */ - useEffect(() => { - if ( - pathnameRef.current !== "/" && - pathnameRef.current !== "/positions" && - pathnameRef.current !== "/activity" && - ((prevChain && chain !== prevChain) || - (prevAddress && address !== prevAddress)) - ) { - MaybeWindow.ifJust((w) => { - const url = new URL(w.location.href); - const newUrl = new URL(w.location.origin); - if (url.searchParams.has("embed")) { - newUrl.searchParams.set("embed", "true"); - } - - w.history.pushState({}, w.document.title, newUrl.href); - navigateRef.current("/", { replace: true }); - }); - } - }, [chain, address, pathnameRef, navigateRef, prevChain, prevAddress]); - - const initTab = useInitParams().data?.tab; - - useEffect(() => { - if (!initTab) return; - - navigateRef.current(initTab === "earn" ? "/" : "/positions"); - }, [initTab, navigateRef]); - - useHandleDeepLinks(); - - const detailsMatch = useDetailsMatch(); - - /** - * Dont unmount details page with tabs - * Handle position details pages in their own Routes - */ - const key = detailsMatch ? "/" : current.key; - - if (underMaintenance) return ; - - return ( - <> - - - -
- - - - - - }> - {/* Home + Tabs */} - }> - } /> - } - /> - } /> - - - }> - {/* Activity flow */} - - } /> - } - /> - } - /> - - - {/* Stake flow */} - - } /> - } /> - } /> - - - {/* Unstake or pending actions flow */} - - } /> - } - /> - - {/* Unstaking */} - - } /> - } /> - } - /> - - - {/* Pending Actions */} - - } /> - } /> - } - /> - - - - - } /> - - - - - - - - - - - - ); -}; diff --git a/packages/widget/src/app/composition/application-route-content.tsx b/packages/widget/src/app/composition/application-route-content.tsx new file mode 100644 index 000000000..17232cb93 --- /dev/null +++ b/packages/widget/src/app/composition/application-route-content.tsx @@ -0,0 +1,10 @@ +import { createContext, type ReactNode, useContext } from "react"; + +const ApplicationRouteContentContext = createContext(null); + +export const ApplicationRouteContentProvider = + ApplicationRouteContentContext.Provider; + +export const ApplicationRouteRoot = () => { + return useContext(ApplicationRouteContentContext); +}; diff --git a/packages/widget/src/app/composition/providers/atom-runtime/index.tsx b/packages/widget/src/app/composition/providers/atom-runtime/index.tsx new file mode 100644 index 000000000..d9ddc1b6b --- /dev/null +++ b/packages/widget/src/app/composition/providers/atom-runtime/index.tsx @@ -0,0 +1,43 @@ +import { RegistryProvider, useAtomSet } from "@effect/atom-react"; +import { type PropsWithChildren, useLayoutEffect } from "react"; +import type { RouteObject } from "react-router"; +import type { WidgetConfig } from "../../../../services/config/widget-config"; +import { config } from "../../../../shared/config/widget-defaults"; +import { makeWidgetRuntimeGenerationKey } from "../../../config/runtime-generation"; +import { widgetConfigAtom } from "../../../config/settings"; +import { applicationRoutesAtom } from "../../../runtime/application-router-runtime"; + +export const SKAtomRegistryProvider = ({ + children, + routes, + settings, +}: PropsWithChildren<{ + readonly routes: ReadonlyArray; + readonly settings: WidgetConfig; +}>) => { + return ( + + {children} + + ); +}; + +const WidgetConfigBinding = ({ + children, + settings, +}: PropsWithChildren<{ readonly settings: WidgetConfig }>) => { + const setWidgetConfig = useAtomSet(widgetConfigAtom); + + useLayoutEffect(() => { + setWidgetConfig(settings); + }, [setWidgetConfig, settings]); + + return children; +}; diff --git a/packages/widget/src/app/composition/providers/index.tsx b/packages/widget/src/app/composition/providers/index.tsx new file mode 100644 index 000000000..9768cdeaf --- /dev/null +++ b/packages/widget/src/app/composition/providers/index.tsx @@ -0,0 +1,85 @@ +import { useAtomValue } from "@effect/atom-react"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import type { ComponentProps, PropsWithChildren } from "react"; +import { StrictMode } from "react"; +import { I18nextProvider } from "react-i18next"; +import { WagmiConfigProvider } from "../../../features/wallet/react/provider"; +import { CurrentLayoutProvider } from "../../../features/widget-shell/current-layout"; +import { selectAtom } from "../../../shared/effect/select-atom"; +import { SKLocationProvider } from "../../../shared/react/location-history"; +import { RootElementProvider } from "../../../shared/react/root-element"; +import { i18nInstance } from "../../../translation"; +import utilaTranslations from "../../../translation/English/utila-variant.json"; +import { widgetConfigAtom } from "../../config/settings"; +import { MountAnimationEffects } from "./mount-animation"; +import { ThirdPartyQueryClientProvider } from "./query-client"; +import { RainbowProvider } from "./rainbow"; +import { ThemeWrapper } from "./theme-wrapper"; + +const widgetTranslationConfigAtom = selectAtom( + widgetConfigAtom, + ({ customTranslations, language, variant }) => ({ + customTranslations, + language, + variant, + }) +); + +const widgetTranslationEffectsAtom = Atom.make((get) => { + const { customTranslations, language, variant } = get( + widgetTranslationConfigAtom + ); + + if (language) { + void i18nInstance.changeLanguage(language); + } + + if (variant === "utila") { + i18nInstance.addResourceBundle( + "en", + "translation", + utilaTranslations, + true, + true + ); + } + + if (customTranslations) { + Object.entries(customTranslations).forEach(([language, value]) => { + i18nInstance.addResourceBundle( + language, + "translation", + value.translation, + true, + true + ); + }); + } +}).pipe(Atom.withLabel("widgetTranslationEffectsAtom")); + +export const Providers = ({ + children, +}: PropsWithChildren & ComponentProps) => { + useAtomValue(widgetTranslationEffectsAtom); + + return ( + + + + + + + + + + {children} + + + + + + + + + ); +}; diff --git a/packages/widget/src/app/composition/providers/mount-animation/index.tsx b/packages/widget/src/app/composition/providers/mount-animation/index.tsx new file mode 100644 index 000000000..7a0c8293e --- /dev/null +++ b/packages/widget/src/app/composition/providers/mount-animation/index.tsx @@ -0,0 +1,30 @@ +import { useEffect, useRef } from "react"; +import { useMountAnimation } from "../../../../features/mount-animation/react/use-mount-animation"; +import { delayAPIRequests } from "../../../../services/api/delay-api-requests"; +import { useSKLocation } from "../../../../shared/react/location-history"; +import { useWidgetConfig } from "../../../config/use-widget-config"; + +const removeDelay = delayAPIRequests(); + +export const MountAnimationEffects = () => { + const onMountAnimationComplete = useWidgetConfig("onMountAnimationComplete"); + const callbackRef = useRef(onMountAnimationComplete); + callbackRef.current = onMountAnimationComplete; + const { current } = useSKLocation(); + const { dispatch, state } = useMountAnimation(); + + useEffect(() => { + if (state.layout && state.earnPage) { + removeDelay(); + callbackRef.current?.(); + } + }, [state.earnPage, state.layout]); + + useEffect(() => { + if (current.pathname !== "/") { + dispatch({ type: "all" }); + } + }, [current.pathname, dispatch]); + + return null; +}; diff --git a/packages/widget/src/app/composition/providers/query-client/index.tsx b/packages/widget/src/app/composition/providers/query-client/index.tsx new file mode 100644 index 000000000..6e023ca62 --- /dev/null +++ b/packages/widget/src/app/composition/providers/query-client/index.tsx @@ -0,0 +1,14 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { PropsWithChildren } from "react"; +import { useState } from "react"; + +/** Third-party infrastructure for Wagmi/RainbowKit; widget API state is forbidden here. */ +export const ThirdPartyQueryClientProvider = ({ + children, +}: PropsWithChildren) => { + const [queryClient] = useState(() => new QueryClient()); + + return ( + {children} + ); +}; diff --git a/packages/widget/src/app/composition/providers/rainbow-kit.tsx b/packages/widget/src/app/composition/providers/rainbow-kit.tsx new file mode 100644 index 000000000..44933cc6e --- /dev/null +++ b/packages/widget/src/app/composition/providers/rainbow-kit.tsx @@ -0,0 +1,134 @@ +import type { DisclaimerComponent } from "@stakekit/rainbowkit"; +import { + RainbowKitProvider, + useChainModal, + useConnectModal, +} from "@stakekit/rainbowkit"; +import type { PropsWithChildren } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { shouldShowDisconnect } from "../../../domain/types/connectors"; +import { useTrackEvent } from "../../../features/tracking/react/use-track-event"; +import { useLedgerDisabledChain } from "../../../features/wallet/react/use-ledger-disabled-chains"; +import { useSKWallet } from "../../../features/wallet/react/use-wallet"; +import { walletModalAdapterAtom } from "../../../features/wallet/state/wallet-modal"; +import { addLedgerAccountAtom } from "../../../features/wallet/state/workflows"; +import { isLedgerLiveConnector } from "../../../services/wallet/connectors/ledger/ledger-live-connector-meta"; +import { vars } from "../../../shared/styles/theme/contract.css"; +import { id } from "../../../shared/styles/theme/ids"; +import type { ConnectKitTheme } from "../../../shared/styles/tokens/connect-kit"; +import { connectKitTheme } from "../../../shared/styles/tokens/connect-kit"; +import { Text } from "../../../shared/ui/primitives/typography/text"; +import { useWidgetConfig } from "../../config/use-widget-config"; + +const finalTheme: ConnectKitTheme = { + ...connectKitTheme.lightMode, + colors: vars.color.connectKit, // ThemeWrapper applies final light/dark colors + radii: vars.borderRadius.connectKit, + fonts: { body: vars.font.body }, +}; + +export const RainbowKitProviderWithTheme = ({ + children, +}: PropsWithChildren) => { + const { connector, connectorChains } = useSKWallet(); + + const portalContainer = useWidgetConfig("portalContainer"); + + const ledgerDisabledChains = useLedgerDisabledChain(); + + const trackEvent = useTrackEvent(); + + const addLedgerAccount = useAtomSet(addLedgerAccountAtom); + + const { t, i18n } = useTranslation(); + + const hideAccountAndChainSelector = useWidgetConfig( + "hideAccountAndChainSelector" + ); + const initialChain = useWidgetConfig("initialChain"); + + const chainIdsToUse = useMemo( + () => new Set(connectorChains.map((c) => c.id)), + [connectorChains] + ); + + const disabledChains = useMemo( + () => + ledgerDisabledChains.map((c) => ({ + ...c, + info: t("chain_modal.disabled_chain_info"), + })), + [ledgerDisabledChains, t] + ); + + const hideDisconnect = useMemo( + () => (connector ? !shouldShowDisconnect(connector) : true), + [connector] + ); + + const locale = useMemo(() => { + if (i18n.language === "fr" || i18n.language === "fr-FR") { + return i18n.language; + } + + return "en"; + }, [i18n.language]); + + return ( + { + trackEvent("addLedgerAccountClicked"); + addLedgerAccount({ + chain: disabledChain, + connector: + connector && isLedgerLiveConnector(connector) ? connector : null, + }); + }} + locale={locale} + appInfo={{ disclaimer: Disclamer, appName: t("shared.stake_kit") }} + {...(hideAccountAndChainSelector && { avatar: null })} + showRecentTransactions={false} + initialChain={initialChain} + theme={finalTheme} + hideDisconnect={hideDisconnect} + dialogRoot={portalContainer} + > + + {children} + + ); +}; + +const WalletModalBoundary = () => { + const updateAdapter = useAtomSet(walletModalAdapterAtom); + const [owner] = useState(() => ({})); + const { closeChainModal } = useChainModal(); + const { openConnectModal } = useConnectModal(); + + useEffect(() => { + updateAdapter({ + _tag: "Install", + adapter: { + closeChain: closeChainModal, + openConnect: () => openConnectModal?.(), + }, + owner, + }); + return () => { + updateAdapter({ _tag: "Uninstall", owner }); + }; + }, [closeChainModal, openConnectModal, owner, updateAdapter]); + return null; +}; + +const Disclamer: DisclaimerComponent = () => { + const { t } = useTranslation(); + return {t("chain_modal_disclaimer")}; +}; + +import { useAtomSet } from "@effect/atom-react"; diff --git a/packages/widget/src/app/composition/providers/rainbow/index.tsx b/packages/widget/src/app/composition/providers/rainbow/index.tsx new file mode 100644 index 000000000..d2d9772c0 --- /dev/null +++ b/packages/widget/src/app/composition/providers/rainbow/index.tsx @@ -0,0 +1,49 @@ +import { useAtomSet } from "@effect/atom-react"; +import { AccountExtraInfoContext } from "@stakekit/rainbowkit"; +import type { PropsWithChildren } from "react"; +import type { Address } from "viem"; +import { useSKWallet } from "../../../../features/wallet/react/use-wallet"; +import type { WalletSwitchAccountInput } from "../../../../services/wallet/domain/commands"; +import { WalletService } from "../../../../services/wallet/wallet-service"; +import { formatAddress } from "../../../../shared/lib/general"; +import { walletRuntime } from "../../../runtime/wallet-runtime"; +import { RainbowKitProviderWithTheme } from "../rainbow-kit"; + +const switchLedgerAccountAtom = walletRuntime.fn( + (input: WalletSwitchAccountInput) => + WalletService.use((wallet) => wallet.switchAccount(input)) +); + +export const RainbowProvider = ({ children }: PropsWithChildren) => { + const wallet = useSKWallet(); + const switchAccount = useAtomSet(switchLedgerAccountAtom, { + mode: "promise", + }); + + const otherAddresses = + wallet.ledgerAccounts + ?.filter((account) => account.address !== wallet.address) + .map((account) => formatAddress(account.address) as Address) ?? []; + + return ( + { + const account = wallet.ledgerAccounts?.find( + (candidate) => formatAddress(candidate.address) === selectedAddress + ); + + if (account && wallet.isConnected) { + void switchAccount({ + account, + connector: wallet.connector, + }).catch(() => undefined); + } + }, + }} + > + {children} + + ); +}; diff --git a/packages/widget/src/app/composition/providers/theme-wrapper.tsx b/packages/widget/src/app/composition/providers/theme-wrapper.tsx new file mode 100644 index 000000000..f6fa3c5b1 --- /dev/null +++ b/packages/widget/src/app/composition/providers/theme-wrapper.tsx @@ -0,0 +1,62 @@ +import { assignInlineVars } from "@vanilla-extract/dynamic"; +import merge from "lodash.merge"; +import type { PropsWithChildren } from "react"; +import { useMemo } from "react"; +import type { WidgetConfig } from "../../../services/config/widget-config"; +import { vars } from "../../../shared/styles/theme/contract.css"; +import { rootSelector } from "../../../shared/styles/theme/ids"; +import { lightTheme } from "../../../shared/styles/theme/themes"; +import { getFineryThemeOverrides } from "../../../shared/styles/theme/variant-overrides/finery"; +import { portoThemeOverrides } from "../../../shared/styles/theme/variant-overrides/porto"; +import { utilaThemeOverrides } from "../../../shared/styles/theme/variant-overrides/utila"; +import type { RecursivePartial } from "../../../shared/types/utils"; +import { useWidgetConfig } from "../../config/use-widget-config"; + +export const getThemeOverrides = ({ + baseTheme, + variant, +}: { + baseTheme: typeof lightTheme; + variant: WidgetConfig["variant"]; +}): RecursivePartial => { + if (variant === "utila") { + return utilaThemeOverrides; + } + + if (variant === "finery") { + return getFineryThemeOverrides(baseTheme); + } + + if (variant === "porto") { + return portoThemeOverrides; + } + + return {}; +}; + +export const ThemeWrapper = ({ children }: PropsWithChildren) => { + const theme = useWidgetConfig("theme"); + const variant = useWidgetConfig("variant"); + + const finalTheme = useMemo(() => { + const baseTheme = merge(structuredClone(lightTheme), theme); + const overrides = getThemeOverrides({ + baseTheme, + variant, + }); + + return merge(structuredClone(lightTheme), theme, overrides); + }, [theme, variant]); + + return ( + <> + + + + ); + + const wrapper = app.container.querySelector( + '[data-rk="token-logo"]' + ); + const image = wrapper?.querySelector("img"); + + expect(wrapper?.getBoundingClientRect().width).toBe(24); + expect(wrapper?.getBoundingClientRect().height).toBe(24); + expect(image?.getBoundingClientRect().width).toBe(24); + expect(image?.getBoundingClientRect().height).toBe(24); + }); +}); diff --git a/packages/widget/tests/components/atoms/token-icon-image.dom.test.tsx b/packages/widget/tests/components/atoms/token-icon-image.dom.test.tsx new file mode 100644 index 000000000..c2023b4d5 --- /dev/null +++ b/packages/widget/tests/components/atoms/token-icon-image.dom.test.tsx @@ -0,0 +1,46 @@ +import { act } from "react"; +import { describe, expect, it } from "vitest"; +import { TokenIconImage } from "../../../src/features/widget-shell/ui/token-icon/token-icon-image"; +import { render } from "../../utils/test-utils.dom"; + +const validSrc = + "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1'%20height='1'%3E%3C/svg%3E"; +const brokenSrcOne = "data:image/png;base64,dG9rZW4x"; + +describe("TokenIconImage", () => { + it("uses a single image element and prefers mainUrl when present", async () => { + const app = await render( + + ); + + expect(app.container.querySelectorAll("img")).toHaveLength(1); + + const image = app.container.querySelector("img"); + + expect(image).not.toBeNull(); + expect(image?.getAttribute("src")).toBe(validSrc); + }); + + it("falls through to the generated monogram after mainUrl fails", async () => { + const app = await render( + + ); + + const image = app.container.querySelector("img"); + + expect(image).not.toBeNull(); + act(() => image?.dispatchEvent(new Event("error"))); + + expect(image?.getAttribute("src")).toContain("data:image/svg+xml"); + }); +}); diff --git a/packages/widget/tests/components/atoms/token-icon-image.test.tsx b/packages/widget/tests/components/atoms/token-icon-image.test.tsx deleted file mode 100644 index ecfe01126..000000000 --- a/packages/widget/tests/components/atoms/token-icon-image.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { TokenIconImage } from "../../../src/components/atoms/token-icon/token-icon-image"; -import { describe, expect, it } from "../../utils/test-extend"; -import { render } from "../../utils/test-utils"; - -const validSrc = - "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1'%20height='1'%3E%3C/svg%3E"; -const brokenSrcOne = "data:image/png;base64,dG9rZW4x"; - -describe("TokenIconImage", () => { - it("uses a single image element and prefers mainUrl when present", async () => { - const app = await render( - - ); - - expect(app.container.querySelectorAll("img")).toHaveLength(1); - - const image = app.container.querySelector("img"); - - expect(image).not.toBeNull(); - expect(image?.getAttribute("src")).toBe(validSrc); - }); - - it("sizes the image from a host-overridden wrapper", async () => { - const app = await render( -
- - -
- ); - - const wrapper = app.container.querySelector( - '[data-rk="token-logo"]' - ); - const image = wrapper?.querySelector("img"); - - expect(wrapper?.getBoundingClientRect().width).toBe(24); - expect(wrapper?.getBoundingClientRect().height).toBe(24); - expect(image?.getBoundingClientRect().width).toBe(24); - expect(image?.getBoundingClientRect().height).toBe(24); - }); - - it("falls through to the generated monogram after mainUrl fails", async () => { - const app = await render( - - ); - - const image = app.container.querySelector("img"); - - expect(image).not.toBeNull(); - image?.dispatchEvent(new Event("error")); - - expect(image?.getAttribute("src")).toContain("data:image/svg+xml"); - }); -}); diff --git a/packages/widget/tests/components/select-validator-section.browser.test.tsx b/packages/widget/tests/components/select-validator-section.browser.test.tsx new file mode 100644 index 000000000..d968728a0 --- /dev/null +++ b/packages/widget/tests/components/select-validator-section.browser.test.tsx @@ -0,0 +1,190 @@ +import { I18nextProvider } from "react-i18next"; +import { describe, expect, it, vi } from "vitest"; +import { userEvent } from "vitest/browser"; +import type { EarnYieldWithProvider } from "../../src/domain/schema/earn-models"; +import { SelectValidatorSection } from "../../src/features/earn/ui/classic/earn-page/components/select-validator-section"; +import type { useSelectValidator } from "../../src/features/earn/ui/classic/earn-page/components/select-validator-section/use-select-validator"; +import { i18nInstance } from "../../src/translation"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; +import { render } from "../utils/test-utils"; +import { decodeValidator } from "../utils/validators"; +import { TestWidgetConfigProvider } from "../utils/widget-config-provider"; + +const hookState = vi.hoisted(() => ({ + current: undefined as unknown as ReturnType, +})); + +vi.mock( + "../../src/features/earn/ui/classic/earn-page/components/select-validator-section/use-select-validator", + () => ({ + useSelectValidator: () => hookState.current, + }) +); + +const baseYield = yieldApiYieldFixture(); +const selectedStake = { + ...baseYield, + mechanics: { + ...baseYield.mechanics, + requiresValidatorSelection: true, + arguments: { + enter: { + fields: { + validatorAddress: { + required: true, + }, + }, + }, + exit: { fields: {} }, + }, + }, +} satisfies EarnYieldWithProvider; + +const multiSelectStake = { + ...selectedStake, + mechanics: { + ...selectedStake.mechanics, + arguments: { + ...selectedStake.mechanics.arguments, + enter: { + fields: { + validatorAddresses: { + required: true, + }, + }, + }, + }, + }, +} satisfies EarnYieldWithProvider; + +const createHookValue = ( + overrides: Partial> = {} +): ReturnType => ({ + isLoading: false, + onViewMoreClick: vi.fn(), + onClose: vi.fn(), + onOpen: vi.fn(), + onItemClick: vi.fn(), + onRemoveValidator: vi.fn(), + selectedValidators: new Map(), + selectedStake, + onValidatorSearch: vi.fn(), + validatorsData: [decodeValidator(yieldApiValidatorFixture())], + validatorSearch: "", + hasMoreValidators: false, + isLoadingMoreValidators: false, + onLoadMoreValidators: vi.fn(), + ...overrides, +}); + +const renderSection = () => + render( + + + + + + ); + +describe("SelectValidatorSection", () => { + it("keeps validator selection available when search has no results", async () => { + hookState.current = createHookValue({ + isLoading: false, + validatorsData: [], + validatorSearch: "missing validator", + }); + + const app = await renderSection(); + + await expect.element(app.getByText("Select validator")).toBeInTheDocument(); + + const trigger = app.container.querySelector("button"); + expect(trigger).not.toBeNull(); + + await userEvent.click(trigger as HTMLButtonElement); + + await expect + .element(app.getByTestId("select-modal__search-input")) + .toHaveValue("missing validator"); + await expect + .element(app.getByText("No validators found")) + .toBeInTheDocument(); + }); + + it("keeps validator selection available while initial validators load", async () => { + hookState.current = createHookValue({ + isLoading: true, + validatorsData: null, + validatorSearch: "", + }); + + const app = await renderSection(); + + await expect.element(app.getByText("Select validator")).toBeInTheDocument(); + + const trigger = app.container.querySelector("button"); + expect(trigger).not.toBeNull(); + + await userEvent.click(trigger as HTMLButtonElement); + + await expect + .element(app.getByTestId("select-modal__search-input")) + .toBeInTheDocument(); + await expect + .element(app.getByText("No validators available")) + .not.toBeInTheDocument(); + }); + + it("shows an empty state in the dialog when no validators are available", async () => { + hookState.current = createHookValue({ + isLoading: false, + validatorsData: [], + validatorSearch: "", + }); + + const app = await renderSection(); + + const trigger = app.container.querySelector("button"); + expect(trigger).not.toBeNull(); + + await userEvent.click(trigger as HTMLButtonElement); + + await expect + .element(app.getByText("No validators available")) + .toBeInTheDocument(); + }); + + it("shows the View all action for multi-select validators when more pages exist", async () => { + hookState.current = createHookValue({ + selectedStake: multiSelectStake, + validatorsData: Array.from({ length: 4 }, (_, index) => + decodeValidator( + yieldApiValidatorFixture({ + address: `validator-${index}`, + name: `Validator ${index}`, + preferred: true, + }) + ) + ), + hasMoreValidators: true, + }); + + const app = await renderSection(); + + await expect.element(app.getByText("Earn with")).toBeInTheDocument(); + + const trigger = app.container.querySelector( + '[data-rk="select-validator-plus"]' + ); + expect(trigger).not.toBeNull(); + + await userEvent.click(trigger as HTMLButtonElement); + + await expect.element(app.getByText("View all")).toBeInTheDocument(); + }); +}); diff --git a/packages/widget/tests/components/select-validator-section.test.tsx b/packages/widget/tests/components/select-validator-section.test.tsx deleted file mode 100644 index a3f78c176..000000000 --- a/packages/widget/tests/components/select-validator-section.test.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { Maybe } from "purify-ts"; -import { I18nextProvider } from "react-i18next"; -import { vi } from "vitest"; -import { userEvent } from "vitest/browser"; -import type { Yield } from "../../src/domain/types/yields"; -import { SelectValidatorSection } from "../../src/pages/details/earn-page/components/select-validator-section"; -import type { useSelectValidator } from "../../src/pages/details/earn-page/components/select-validator-section/use-select-validator"; -import { SettingsContextProvider } from "../../src/providers/settings"; -import { i18nInstance } from "../../src/translation"; -import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; -import { describe, expect, it } from "../utils/test-extend"; -import { render } from "../utils/test-utils"; - -const hookState = vi.hoisted(() => ({ - current: undefined as unknown as ReturnType, -})); - -vi.mock( - "../../src/pages/details/earn-page/components/select-validator-section/use-select-validator", - () => ({ - useSelectValidator: () => hookState.current, - }) -); - -const baseYield = yieldApiYieldFixture(); -const selectedStake = { - ...baseYield, - mechanics: { - ...baseYield.mechanics, - requiresValidatorSelection: true, - arguments: { - enter: { - fields: [ - { - label: "Validator", - name: "validatorAddress", - required: true, - type: "address", - }, - ], - }, - exit: { fields: [] }, - }, - }, -} as Yield; - -const multiSelectStake = { - ...selectedStake, - mechanics: { - ...selectedStake.mechanics, - arguments: { - ...selectedStake.mechanics.arguments, - enter: { - fields: [ - { - label: "Validators", - name: "validatorAddresses", - required: true, - type: "address", - }, - ], - }, - }, - }, -} as Yield; - -const createHookValue = ( - overrides: Partial> = {} -): ReturnType => ({ - isLoading: false, - onViewMoreClick: vi.fn(), - onClose: vi.fn(), - onOpen: vi.fn(), - onItemClick: vi.fn(), - onRemoveValidator: vi.fn(), - selectedValidators: new Map(), - selectedStake: Maybe.of(selectedStake), - onValidatorSearch: vi.fn(), - validatorsData: Maybe.of([yieldApiValidatorFixture()]), - validatorSearch: "", - hasMoreValidators: false, - isLoadingMoreValidators: false, - onLoadMoreValidators: vi.fn(), - ...overrides, -}); - -const renderSection = () => - render( - - - - - - ); - -describe("SelectValidatorSection", () => { - it("keeps validator selection available when search has no results", async () => { - hookState.current = createHookValue({ - isLoading: false, - validatorsData: Maybe.of([]), - validatorSearch: "missing validator", - }); - - const app = await renderSection(); - - await expect.element(app.getByText("Select validator")).toBeInTheDocument(); - - const trigger = app.container.querySelector("button"); - expect(trigger).not.toBeNull(); - - await userEvent.click(trigger as HTMLButtonElement); - - await expect - .element(app.getByTestId("select-modal__search-input")) - .toHaveValue("missing validator"); - await expect - .element(app.getByText("No validators found")) - .toBeInTheDocument(); - }); - - it("keeps validator selection available while initial validators load", async () => { - hookState.current = createHookValue({ - isLoading: true, - validatorsData: Maybe.empty(), - validatorSearch: "", - }); - - const app = await renderSection(); - - await expect.element(app.getByText("Select validator")).toBeInTheDocument(); - - const trigger = app.container.querySelector("button"); - expect(trigger).not.toBeNull(); - - await userEvent.click(trigger as HTMLButtonElement); - - await expect - .element(app.getByTestId("select-modal__search-input")) - .toBeInTheDocument(); - await expect - .element(app.getByText("No validators available")) - .not.toBeInTheDocument(); - }); - - it("shows an empty state in the dialog when no validators are available", async () => { - hookState.current = createHookValue({ - isLoading: false, - validatorsData: Maybe.of([]), - validatorSearch: "", - }); - - const app = await renderSection(); - - const trigger = app.container.querySelector("button"); - expect(trigger).not.toBeNull(); - - await userEvent.click(trigger as HTMLButtonElement); - - await expect - .element(app.getByText("No validators available")) - .toBeInTheDocument(); - }); - - it("shows the View all action for multi-select validators when more pages exist", async () => { - hookState.current = createHookValue({ - selectedStake: Maybe.of(multiSelectStake), - validatorsData: Maybe.of( - Array.from({ length: 4 }, (_, index) => - yieldApiValidatorFixture({ - address: `validator-${index}`, - name: `Validator ${index}`, - preferred: true, - }) - ) - ), - hasMoreValidators: true, - }); - - const app = await renderSection(); - - await expect.element(app.getByText("Earn with")).toBeInTheDocument(); - - const trigger = app.container.querySelector( - '[data-rk="select-validator-plus"]' - ); - expect(trigger).not.toBeNull(); - - await userEvent.click(trigger as HTMLButtonElement); - - await expect.element(app.getByText("View all")).toBeInTheDocument(); - }); -}); diff --git a/packages/widget/tests/domain/action-models.test.ts b/packages/widget/tests/domain/action-models.test.ts new file mode 100644 index 000000000..07de7ac7b --- /dev/null +++ b/packages/widget/tests/domain/action-models.test.ts @@ -0,0 +1,121 @@ +import BigNumber from "bignumber.js"; +import { Effect, Logger, References, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { + ActionCommand, + TransactionGasEstimateJson, + YieldAction, +} from "../../src/domain/schema/action-models"; +import { + yieldApiActionDtoFixture, + yieldApiTransactionDtoFixture, +} from "../fixtures"; + +const transaction = yieldApiTransactionDtoFixture({ + gasEstimate: JSON.stringify({ + amount: "0.01", + token: { + decimals: 18, + name: "Ethereum", + network: "ethereum", + symbol: "ETH", + }, + }), + id: "transaction-1", + network: "ethereum", +}); + +const action = yieldApiActionDtoFixture({ + address: "0xWallet", + id: "action-1", + transactions: [transaction], + yieldId: "ethereum-eth-native-staking", +}); + +describe("action application schemas", () => { + it("brands command and response identifiers at the application boundary", () => { + const command = Schema.decodeUnknownSync(ActionCommand)({ + address: "0xWallet", + arguments: { amount: "1" }, + yieldId: "ethereum-eth-native-staking", + }); + const model = Schema.decodeUnknownSync(YieldAction)(action); + + expect(command.yieldId).toBe("ethereum-eth-native-staking"); + expect(model.id).toBe("action-1"); + expect(model.transactions[0]?.id).toBe("transaction-1"); + }); + + it("strictly rejects a malformed nested transaction", () => { + expect(() => + Schema.decodeUnknownSync(YieldAction)({ + ...action, + transactions: [{ ...transaction, id: "" }], + }) + ).toThrow(); + }); + + it("rejects invalid required timestamps", async () => { + await expect( + Effect.runPromise( + Schema.decodeUnknownEffect(YieldAction)({ + ...action, + createdAt: "invalid", + }) + ) + ).rejects.toThrow(); + }); + + it("safely clears invalid nullable timestamps and emits structured warnings", async () => { + const annotations: Array> = []; + const logger = Logger.make((options) => { + annotations.push({ + ...options.fiber.getRef(References.CurrentLogAnnotations), + }); + }); + const decoded = await Effect.runPromise( + Schema.decodeUnknownEffect(YieldAction)({ + ...action, + completedAt: "invalid-completion", + transactions: [ + { + ...transaction, + broadcastedAt: "invalid-broadcast", + }, + ], + }).pipe(Effect.provide(Logger.layer([logger]))) + ); + + expect(decoded.completedAt).toBeNull(); + expect(decoded.transactions[0]?.broadcastedAt).toBeNull(); + expect(annotations).toHaveLength(2); + expect(annotations).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + event: "api_decode_field_rejection", + operation: "yield-action", + field: "completedAt", + }), + expect.objectContaining({ + event: "api_decode_field_rejection", + operation: "yield-action-transaction", + field: "broadcastedAt", + }), + ]) + ); + expect(JSON.stringify(annotations)).not.toContain("invalid-completion"); + expect(JSON.stringify(annotations)).not.toContain("invalid-broadcast"); + }); + + it("decodes transaction gas JSON only through Effect Schema", () => { + const gas = Schema.decodeUnknownSync(TransactionGasEstimateJson)( + transaction.gasEstimate + ); + + expect(gas.amount).toBeInstanceOf(BigNumber); + expect(gas.amount.toFixed()).toBe("0.01"); + expect(() => + Schema.decodeUnknownSync(TransactionGasEstimateJson)("not-json") + ).toThrow(); + }); +}); diff --git a/packages/widget/tests/domain/api-boundary-decisions.test.ts b/packages/widget/tests/domain/api-boundary-decisions.test.ts new file mode 100644 index 000000000..9c04420c8 --- /dev/null +++ b/packages/widget/tests/domain/api-boundary-decisions.test.ts @@ -0,0 +1,50 @@ +import BigNumber from "bignumber.js"; +import { Effect, Logger, References, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { logDecodeRejection } from "../../src/domain/schema/decode-diagnostics"; +import { PrecisionDecimalFromString } from "../../src/domain/schema/scalars"; + +describe("API boundary foundation decisions", () => { + it("decodes precision-sensitive decimals to BigNumber without precision loss", () => { + const value = Schema.decodeUnknownSync(PrecisionDecimalFromString)( + "12345678901234567890.123456789012345678" + ); + + expect(BigNumber.isBigNumber(value)).toBe(true); + expect(value.toFixed()).toBe("12345678901234567890.123456789012345678"); + expect(Schema.encodeSync(PrecisionDecimalFromString)(value)).toBe( + "12345678901234567890.123456789012345678" + ); + expect(() => + Schema.decodeUnknownSync(PrecisionDecimalFromString)("NaN") + ).toThrow(); + }); + + it("emits structured warning diagnostics without the rejected value", async () => { + const annotations: Array> = []; + const logger = Logger.make((options) => { + annotations.push({ + ...options.fiber.getRef(References.CurrentLogAnnotations), + }); + }); + + await Effect.runPromise( + logDecodeRejection({ + operation: "yield-catalog", + location: 2, + identifier: "yield-id", + issue: "id is missing", + }).pipe(Effect.provide(Logger.layer([logger]))) + ); + + expect(annotations).toEqual([ + { + event: "api_decode_rejection", + operation: "yield-catalog", + location: "2", + identifier: "yield-id", + issue: "id is missing", + }, + ]); + }); +}); diff --git a/packages/widget/tests/domain/api-boundary-errors.test.ts b/packages/widget/tests/domain/api-boundary-errors.test.ts new file mode 100644 index 000000000..de9b849e8 --- /dev/null +++ b/packages/widget/tests/domain/api-boundary-errors.test.ts @@ -0,0 +1,46 @@ +import { Effect } from "effect"; +import { describe, expect, it } from "vitest"; +import { + ApiRequestError, + type InputValidationError, + ResponseDecodeError, +} from "../../src/domain/schema/api-errors"; + +type ApiBoundaryError = + | ApiRequestError + | InputValidationError + | ResponseDecodeError; + +const classify = (error: ApiBoundaryError) => + Effect.fail(error).pipe( + Effect.catchTags({ + ApiRequestError: () => Effect.succeed("api" as const), + ResponseDecodeError: () => Effect.succeed("decode" as const), + }) + ); + +describe("API boundary errors", () => { + it("distinguishes API, decode, and absence failures by tag", async () => { + await expect( + Effect.runPromise( + classify( + new ApiRequestError({ + operation: "yield-list", + cause: new Error("network"), + }) + ) + ) + ).resolves.toBe("api"); + await expect( + Effect.runPromise( + classify( + new ResponseDecodeError({ + operation: "yield-list", + issue: "missing id", + cause: new Error("schema"), + }) + ) + ) + ).resolves.toBe("decode"); + }); +}); diff --git a/packages/widget/tests/domain/api-response-schema.test.ts b/packages/widget/tests/domain/api-response-schema.test.ts new file mode 100644 index 000000000..3e2e8c338 --- /dev/null +++ b/packages/widget/tests/domain/api-response-schema.test.ts @@ -0,0 +1,179 @@ +import BigNumber from "bignumber.js"; +import { Effect, Logger, References, Schema, SchemaGetter } from "effect"; +import { describe, expect, it } from "vitest"; +import { + TolerantTopLevelArray, + TolerantTopLevelRecord, +} from "../../src/domain/schema/response"; +import { PrecisionDecimalFromString } from "../../src/domain/schema/scalars"; + +const ItemId = Schema.String.pipe(Schema.brand("ResponseSchemaTestItemId")); +const Item = Schema.Struct({ + id: ItemId, + amount: PrecisionDecimalFromString, + nested: Schema.Struct({ enabled: Schema.Boolean }), +}); + +const ItemIdentifier = Schema.Struct({ id: Schema.String }).pipe( + Schema.decodeTo(Schema.String, { + decode: SchemaGetter.transform((value) => value.id), + encode: SchemaGetter.forbidden(() => "Identifier schema is decode-only"), + }) +); + +const Items = TolerantTopLevelArray(Item, { + operation: "test-items", + identifier: ItemIdentifier, +}); + +const Envelope = Schema.Struct({ + items: Items, + total: Schema.Number, + offset: Schema.Number, +}); + +const validItem = (id: string, amount = "1.25") => ({ + id, + amount, + nested: { enabled: true }, +}); + +const captureDiagnostics = (effect: Effect.Effect) => { + const annotations: Array> = []; + const logger = Logger.make((options) => { + annotations.push({ + ...options.fiber.getRef(References.CurrentLogAnnotations), + }); + }); + + return { + annotations, + result: Effect.runPromise( + effect.pipe(Effect.provide(Logger.layer([logger]))) + ), + }; +}; + +describe("API response schemas", () => { + it("strictly decodes a valid single domain model", async () => { + const result = await Effect.runPromise( + Schema.decodeUnknownEffect(Item)(validItem("item-1")) + ); + + expect(result.id).toBe("item-1"); + expect(BigNumber.isBigNumber(result.amount)).toBe(true); + expect(result.amount.toFixed()).toBe("1.25"); + }); + + it("rejects a malformed single domain model", async () => { + await expect( + Effect.runPromise( + Schema.decodeUnknownEffect(Item)({ + ...validItem("item-1"), + nested: { enabled: "yes" }, + }) + ) + ).rejects.toThrow(/Expected boolean/); + }); + + it("rejects a malformed response envelope", async () => { + await expect( + Effect.runPromise( + Schema.decodeUnknownEffect(Envelope)({ + items: [validItem("item-1")], + total: "1", + offset: 0, + }) + ) + ).rejects.toThrow(/Expected number/); + }); + + it("keeps valid items in source order and rejects complete invalid parents", async () => { + const decoded = captureDiagnostics( + Schema.decodeUnknownEffect(Envelope)({ + items: [ + validItem("item-1"), + { + ...validItem("item-nested-invalid"), + nested: { enabled: "yes" }, + }, + validItem("item-2", "9007199254740993.000000000000000001"), + validItem("item-amount-invalid", "NaN"), + ], + total: 4, + offset: 20, + }) + ); + + const result = await decoded.result; + + expect(result.items.map((item) => item.id)).toEqual(["item-1", "item-2"]); + expect(result.items[1]?.amount.toFixed()).toBe( + "9007199254740993.000000000000000001" + ); + expect(result.total).toBe(4); + expect(result.offset).toBe(20); + expect(decoded.annotations).toEqual([ + expect.objectContaining({ + event: "api_decode_rejection", + operation: "test-items", + location: "1", + identifier: "item-nested-invalid", + }), + expect.objectContaining({ + event: "api_decode_rejection", + operation: "test-items", + location: "3", + identifier: "item-amount-invalid", + }), + ]); + expect(JSON.stringify(decoded.annotations)).not.toContain( + 'nested":{"enabled":"yes"}' + ); + }); + + it("returns an empty collection when every top-level item is rejected", async () => { + const result = await Effect.runPromise( + Schema.decodeUnknownEffect(Items)([ + validItem("item-1", "NaN"), + { id: "item-2", amount: "2", nested: { enabled: "yes" } }, + ]) + ); + + expect(result).toEqual([]); + }); + + it("omits an entire key-value entry when its key or value fails", async () => { + const RecordKey = Schema.String.check(Schema.isPattern(/^item-/)).pipe( + Schema.brand("ResponseSchemaTestRecordKey") + ); + const RecordResponse = TolerantTopLevelRecord(RecordKey, Item, { + operation: "test-record", + identifier: ItemIdentifier, + }); + const decoded = captureDiagnostics( + Schema.decodeUnknownEffect(RecordResponse)({ + "item-a": validItem("item-a"), + invalid: validItem("item-invalid-key"), + "item-b": validItem("item-b", "NaN"), + }) + ); + + const result = await decoded.result; + + expect(Object.keys(result)).toEqual(["item-a"]); + expect(Object.values(result)[0]?.id).toBe("item-a"); + expect(decoded.annotations).toEqual([ + expect.objectContaining({ + location: "invalid", + identifier: "item-invalid-key", + issue: expect.stringContaining("key:"), + }), + expect.objectContaining({ + location: "item-b", + identifier: "item-b", + issue: expect.stringContaining("value:"), + }), + ]); + }); +}); diff --git a/packages/widget/tests/domain/app-scalars-identifiers.test.ts b/packages/widget/tests/domain/app-scalars-identifiers.test.ts new file mode 100644 index 000000000..cccc7748f --- /dev/null +++ b/packages/widget/tests/domain/app-scalars-identifiers.test.ts @@ -0,0 +1,51 @@ +import { DateTime, Schema } from "effect"; +import { describe, expect, expectTypeOf, it } from "vitest"; +import { + type ActionId, + type ProviderId, + TokenAddress, + type TransactionId, + type YieldId, +} from "../../src/domain/schema/identifiers"; +import { + BigIntFromString, + PrecisionDecimalFromString, + UtcDateTimeFromString, +} from "../../src/domain/schema/scalars"; + +describe("application scalar and identifier schemas", () => { + it("uses lossless representations for raw units and decimal values", () => { + expect( + Schema.decodeUnknownSync(BigIntFromString)( + "900719925474099312345678901234567890" + ) + ).toBe(900719925474099312345678901234567890n); + expect( + Schema.decodeUnknownSync(PrecisionDecimalFromString)( + "9007199254740993.000000000000000001" + ).toFixed() + ).toBe("9007199254740993.000000000000000001"); + }); + + it("rejects invalid dates and normalizes valid date-times to UTC", () => { + const dateTime = Schema.decodeUnknownSync(UtcDateTimeFromString)( + "2026-07-10T14:00:00+02:00" + ); + + expect(DateTime.formatIso(dateTime)).toBe("2026-07-10T12:00:00.000Z"); + expect(() => + Schema.decodeUnknownSync(UtcDateTimeFromString)("not-a-date") + ).toThrow(); + }); + + it("keeps token addresses opaque", () => { + expect(Schema.decodeUnknownSync(TokenAddress)("CaseSensitiveAddress")).toBe( + "CaseSensitiveAddress" + ); + }); + + it("keeps identifier roles distinct at compile time", () => { + expectTypeOf().not.toEqualTypeOf(); + expectTypeOf().not.toEqualTypeOf(); + }); +}); diff --git a/packages/widget/tests/domain/catalog-utilities.test.ts b/packages/widget/tests/domain/catalog-utilities.test.ts new file mode 100644 index 000000000..5eb1ad590 --- /dev/null +++ b/packages/widget/tests/domain/catalog-utilities.test.ts @@ -0,0 +1,98 @@ +import { Effect } from "effect"; +import { describe, expect, it, vi } from "vitest"; +import { loadAllPagesByIdChunks } from "../../src/shared/effect/pagination"; + +type Item = { + id: string; +}; + +const makeItem = (id: string): Item => ({ id }); + +describe("loadAllPagesByIdChunks", () => { + it("splits IDs into bounded chunks", async () => { + const ids = Array.from({ length: 5 }, (_, index) => `yield-${index}`); + const fetchPage = vi.fn(({ ids }: { ids: ReadonlyArray }) => + Effect.succeed({ + total: ids.length, + items: ids.map(makeItem), + }) + ); + + const result = await Effect.runPromise( + loadAllPagesByIdChunks({ + chunkSize: 2, + concurrency: 2, + fetchPage, + getItemId: (item) => item.id, + ids, + pageSize: 100, + }) + ); + + expect(result.map((item) => item.id)).toEqual(ids); + expect(fetchPage).toHaveBeenCalledTimes(3); + expect(fetchPage.mock.calls.map(([arg]) => arg.ids)).toEqual([ + ["yield-0", "yield-1"], + ["yield-2", "yield-3"], + ["yield-4"], + ]); + }); + + it("deduplicates requested IDs and returns items in first occurrence order", async () => { + const fetchPage = vi.fn(({ ids }: { ids: ReadonlyArray }) => + Effect.succeed({ + total: ids.length, + items: [...ids].reverse().map(makeItem), + }) + ); + + const result = await Effect.runPromise( + loadAllPagesByIdChunks({ + chunkSize: 2, + concurrency: 2, + fetchPage, + getItemId: (item) => item.id, + ids: ["yield-2", "yield-1", "yield-2", "yield-0"], + pageSize: 100, + }) + ); + + expect(result.map((item) => item.id)).toEqual([ + "yield-2", + "yield-1", + "yield-0", + ]); + expect(fetchPage.mock.calls.map(([arg]) => arg.ids)).toEqual([ + ["yield-2", "yield-1"], + ["yield-0"], + ]); + }); + + it("loads additional pages for each chunk", async () => { + const fetchPage = vi.fn( + ({ ids, offset }: { ids: ReadonlyArray; offset: number }) => + Effect.succeed({ + total: ids.length, + items: ids.slice(offset, offset + 2).map(makeItem), + }) + ); + + const result = await Effect.runPromise( + loadAllPagesByIdChunks({ + chunkSize: 3, + concurrency: 2, + fetchPage, + getItemId: (item) => item.id, + ids: ["yield-0", "yield-1", "yield-2"], + pageSize: 2, + }) + ); + + expect(result.map((item) => item.id)).toEqual([ + "yield-0", + "yield-1", + "yield-2", + ]); + expect(fetchPage.mock.calls.map(([arg]) => arg.offset)).toEqual([0, 2]); + }); +}); diff --git a/packages/widget/tests/domain/dashboard-models.test.ts b/packages/widget/tests/domain/dashboard-models.test.ts new file mode 100644 index 000000000..fd44c6482 --- /dev/null +++ b/packages/widget/tests/domain/dashboard-models.test.ts @@ -0,0 +1,95 @@ +import { DateTime, Effect, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { + KycStatus, + RewardRateHistoryResponse, + RewardsSummaryRecord, + TvlHistoryResponse, +} from "../../src/domain/schema/dashboard-models"; + +const envelope = { + from: "2026-06-01T00:00:00.000Z", + interval: "day", + limit: 2, + offset: 0, + to: "2026-07-01T00:00:00.000Z", + total: 2, + yieldId: "ethereum-eth-native-staking", +} as const; + +const token = { + decimals: 18, + name: "Ethereum", + network: "ethereum", + symbol: "ETH", +} as const; + +describe("dashboard application schemas", () => { + it("strictly validates KYC singles", () => { + expect( + Schema.decodeUnknownSync(KycStatus)({ kycStatus: "approved" }) + ).toEqual({ kycStatus: "approved" }); + expect(() => + Schema.decodeUnknownSync(KycStatus)({ kycStatus: "unknown" }) + ).toThrow(); + }); + + it("omits malformed reward-rate and TVL points independently", async () => { + const rewardRate = await Effect.runPromise( + Schema.decodeUnknownEffect(RewardRateHistoryResponse)({ + ...envelope, + items: [ + { timestamp: "2026-06-01T00:00:00.000Z", rewardRate: "0.05" }, + { timestamp: "invalid", rewardRate: "0.07" }, + ], + }) + ); + const tvl = await Effect.runPromise( + Schema.decodeUnknownEffect(TvlHistoryResponse)({ + ...envelope, + items: [ + { + timestamp: "2026-06-01T00:00:00.000Z", + tvl: "1000.5", + tvlRaw: "1000500000", + }, + { + timestamp: "2026-06-02T00:00:00.000Z", + tvl: null, + tvlRaw: "0", + }, + ], + }) + ); + + expect(rewardRate.items).toHaveLength(1); + expect(rewardRate.items[0]?.value).toBe(5); + expect(DateTime.isDateTime(rewardRate.from)).toBe(true); + expect(DateTime.isDateTime(rewardRate.to)).toBe(true); + expect(DateTime.isDateTime(rewardRate.items[0]?.timestamp)).toBe(true); + expect(tvl.items).toHaveLength(1); + expect(tvl.items[0]?.value).toBe(1000.5); + expect(DateTime.isDateTime(tvl.items[0]?.timestamp)).toBe(true); + }); + + it("omits a malformed reward summary while retaining valid siblings", async () => { + const rewards = { + last24H: "0", + last30D: "3", + last7D: "1", + lastYear: "12", + total: "20", + }; + const decoded = await Effect.runPromise( + Schema.decodeUnknownEffect(RewardsSummaryRecord)({ + "ethereum-eth-native-staking": { rewards, token }, + "cosmos-atom-native-staking": { + rewards, + token: { ...token, decimals: "invalid" }, + }, + }) + ); + + expect(Object.keys(decoded)).toEqual(["ethereum-eth-native-staking"]); + }); +}); diff --git a/packages/widget/tests/domain/dashboard-yield-category-types.test.ts b/packages/widget/tests/domain/dashboard-yield-category-types.test.ts index 6ab60bb4c..572c39c74 100644 --- a/packages/widget/tests/domain/dashboard-yield-category-types.test.ts +++ b/packages/widget/tests/domain/dashboard-yield-category-types.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from "vitest"; +import type { EarnYieldWithProvider } from "../../src/domain/schema/earn-models"; import { - DashboardYieldCategory, dashboardYieldCategories, getApiYieldTypesForDashboardCategory, getDashboardYieldCategory, getYieldTypeLabels, getYieldTypesSortRank, normalizeDashboardYieldCategoryOrder, - type YieldBase, } from "../../src/domain/types/yields"; +import { DashboardYieldCategory } from "../../src/public-api/types"; const allApiYieldTypes = [ "staking", @@ -22,7 +22,7 @@ const allApiYieldTypes = [ "liquid_staking", ] as const; -const makeYield = (type: string): YieldBase => +const makeYield = (type: string): EarnYieldWithProvider => ({ mechanics: { type, @@ -31,7 +31,7 @@ const makeYield = (type: string): YieldBase => network: "ethereum", symbol: "USDC", }, - }) as YieldBase; + }) as EarnYieldWithProvider; const t = ((key: string) => { const values: Record = { diff --git a/packages/widget/tests/domain/earn-boundary.test.ts b/packages/widget/tests/domain/earn-boundary.test.ts new file mode 100644 index 000000000..14d31bdba --- /dev/null +++ b/packages/widget/tests/domain/earn-boundary.test.ts @@ -0,0 +1,236 @@ +import { Effect, Logger, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { + EarnLegacyTokenOptionsResponse, + EarnPositionsResponse, + EarnProvider, + EarnTokenPage, + EarnValidatorPage, + EarnYield, + EarnYieldPage, +} from "../../src/domain/schema/earn-models"; +import { TokenBalancesResponse } from "../../src/domain/schema/financial-models"; +import { resolveYieldOptions } from "../../src/features/earn/state/atoms-state/resolver/yield"; +import { yieldApiYieldDtoFixture } from "../fixtures"; + +const token = { + name: "Ethereum", + symbol: "ETH", + decimals: 18, + network: "ethereum", + address: "0xAbCd", +} as const; + +const decode = >( + schema: S, + input: unknown +) => + Effect.runPromise( + Schema.decodeUnknownEffect(schema)(input).pipe( + Effect.provide(Logger.layer([])) + ) + ); + +describe("Earn API boundary policies", () => { + it("keeps valid catalog entries and omits a complete nested-invalid yield", async () => { + const valid = yieldApiYieldDtoFixture({ prime: false }); + const result = await decode(EarnYieldPage, { + items: [ + valid, + { + ...valid, + id: "invalid-yield", + token: { ...valid.token, decimals: "18" }, + }, + ], + limit: 100, + offset: 0, + total: 2, + }); + + expect(result.items?.map((item) => item.id)).toEqual([valid.id]); + expect(result.total).toBe(2); + }); + + it("omits only the yield with an invalid consumed mechanic argument", async () => { + const valid = yieldApiYieldDtoFixture({ prime: false }); + const result = await decode(EarnYieldPage, { + items: [ + valid, + { + ...valid, + id: "invalid-mechanic-yield", + mechanics: { + ...valid.mechanics, + arguments: { + enter: { + fields: [ + { + label: "Amount", + minimum: "not-a-number", + name: "amount", + type: "string", + }, + ], + }, + }, + }, + }, + ], + limit: 100, + offset: 0, + total: 2, + }); + + expect(result.items?.map((item) => item.id)).toEqual([valid.id]); + }); + + it("returns an empty catalog when every top-level yield is invalid", async () => { + const valid = yieldApiYieldDtoFixture({ prime: false }); + const result = await decode(EarnYieldPage, { + items: [{ ...valid, id: "invalid-yield", prime: "no" }], + limit: 100, + offset: 0, + total: 1, + }); + + expect(result.items).toEqual([]); + }); + + it("strictly rejects a malformed initial yield", async () => { + await expect( + decode(EarnYield, { + ...yieldApiYieldDtoFixture({ prime: false }), + metadata: null, + }) + ).rejects.toThrow(); + }); + + it("strictly rejects a malformed provider", async () => { + await expect( + decode(EarnProvider, { + id: "", + name: "StakeKit", + description: "", + logoURI: + "https://assets.stakek.it/app/composition/providers/stakekit.svg", + website: "https://stakek.it", + tvlUsd: null, + type: "protocol", + }) + ).rejects.toThrow(); + }); + + it("omits malformed complete token options and balances", async () => { + const validOption = { + availableYields: ["ethereum-eth-native-staking"], + token, + }; + const malformedOption = { + ...validOption, + token: { ...token, decimals: "18" }, + }; + const [page, legacyOptions, balances] = await Promise.all([ + decode(EarnTokenPage, { + items: [validOption, malformedOption], + limit: 2, + offset: 0, + total: 2, + }), + decode(EarnLegacyTokenOptionsResponse, [validOption, malformedOption]), + decode(TokenBalancesResponse, [ + { ...validOption, amount: "1.5" }, + { ...malformedOption, amount: "2" }, + ]), + ]); + + expect(page.items).toHaveLength(1); + expect(page.total).toBe(2); + expect(legacyOptions).toHaveLength(1); + expect(balances).toHaveLength(1); + expect(balances[0]?.amount.toFixed()).toBe("1.5"); + }); + + it("partially decodes validators and derives stable keys", async () => { + const result = await decode(EarnValidatorPage, { + items: [ + { address: "validator-1", subnet: { id: 7 } }, + { name: "missing-address" }, + ], + limit: 100, + offset: 0, + total: 2, + }); + + expect(result.items?.map((item) => item.key)).toEqual(["validator-1:7"]); + }); + + it("rejects a complete position when a nested balance is invalid", async () => { + const result = await decode(EarnPositionsResponse, { + errors: [], + items: [ + { + yieldId: "ethereum-eth-native-staking", + balances: [ + { + address: "wallet-1", + type: "active", + amount: "1.5", + amountRaw: "1500000000000000000", + pendingActions: [], + token, + isEarning: true, + }, + { + address: "wallet-1", + type: "active", + amount: "NaN", + amountRaw: "1", + pendingActions: [], + token, + isEarning: true, + }, + ], + }, + ], + }); + + expect(result.items).toEqual([]); + }); + + it("preserves existing token-scoped yield selection behavior", async () => { + const yieldModel = await decode( + EarnYield, + yieldApiYieldDtoFixture({ prime: false }) + ); + const options = resolveYieldOptions({ + selectedToken: { + token: yieldModel.token, + availableYields: [yieldModel.id], + amount: "0", + source: "default", + }, + yieldsById: [yieldModel], + }); + + expect(options.map((option) => option.id)).toEqual([yieldModel.id]); + }); + + it("does not hide an API-scoped yield by hard-coded identifier", async () => { + const yieldModel = await decode( + EarnYield, + yieldApiYieldDtoFixture({ id: "avax-native-staking" }) + ); + const options = resolveYieldOptions({ + selectedToken: { + token: yieldModel.token, + availableYields: [yieldModel.id], + amount: "0", + source: "default", + }, + yieldsById: [yieldModel], + }); + + expect(options.map((option) => option.id)).toEqual([yieldModel.id]); + }); +}); diff --git a/packages/widget/tests/domain/earn-models.test.ts b/packages/widget/tests/domain/earn-models.test.ts new file mode 100644 index 000000000..1129acac5 --- /dev/null +++ b/packages/widget/tests/domain/earn-models.test.ts @@ -0,0 +1,514 @@ +import BigNumber from "bignumber.js"; +import { Effect, Logger, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { + EarnBalance, + EarnPosition, + EarnProvider, + EarnToken, + EarnValidator, + EarnYield, +} from "../../src/domain/schema/earn-models"; +import { yieldApiYieldDtoFixture, yieldApiYieldFixture } from "../fixtures"; + +const token = { + name: "Ethereum", + symbol: "ETH", + decimals: 18, + network: "ethereum", + address: "0xAbCd", +} as const; + +describe("Earn application models", () => { + it("brands token/provider identifiers without changing case-sensitive addresses", () => { + const decodedToken = Schema.decodeUnknownSync(EarnToken)(token); + const provider = Schema.decodeUnknownSync(EarnProvider)({ + id: "stakekit", + name: "StakeKit", + description: "", + logoURI: + "https://assets.stakek.it/app/composition/providers/stakekit.svg", + website: "https://stakek.it", + tvlUsd: null, + type: "protocol", + }); + + expect(decodedToken.address).toBe("0xAbCd"); + expect(provider.id).toBe("stakekit"); + }); + + it("derives stable validator keys through Schema", () => { + const validator = Schema.decodeUnknownSync(EarnValidator)({ + address: "validator-1", + subnet: { id: 7, name: "Subnet 7" }, + }); + + expect(validator.key).toBe("validator-1:7"); + }); + + it("uses lossless balance amount and raw-unit representations", () => { + const balance = Schema.decodeUnknownSync(EarnBalance)({ + address: "wallet-1", + type: "active", + amount: "9007199254740993.000000000000000001", + amountRaw: "9007199254740993000000000000000001", + pendingActions: [], + token, + amountUsd: "12345678901234567890.12", + isEarning: true, + }); + + expect(BigNumber.isBigNumber(balance.amount)).toBe(true); + expect(balance.amount.toFixed()).toBe( + "9007199254740993.000000000000000001" + ); + expect(balance.amountRaw).toBe(9007199254740993000000000000000001n); + expect(balance.amountUsd?.toFixed()).toBe("12345678901234567890.12"); + }); + + it("safely omits an invalid optional balance timestamp", async () => { + const balance = await Effect.runPromise( + Schema.decodeUnknownEffect(EarnBalance)({ + address: "wallet-1", + type: "active", + amount: "1", + amountRaw: "1", + date: "invalid", + pendingActions: [], + token, + isEarning: true, + }).pipe(Effect.provide(Logger.layer([]))) + ); + + expect(balance.date).toBeUndefined(); + }); + + it("decodes complete yield and position models with branded yield IDs", () => { + const yieldModel = yieldApiYieldFixture({ prime: false }); + const position = Schema.decodeUnknownSync(EarnPosition)({ + yieldId: yieldModel.id, + balances: [ + { + address: "wallet-1", + type: "active", + amount: "1.5", + amountRaw: "1500000000000000000", + pendingActions: [], + token, + isEarning: true, + }, + ], + outputTokenBalance: null, + }); + + expect(yieldModel.id).toBe("ethereum-eth-native-staking"); + expect(position.yieldId).toBe(yieldModel.id); + expect(position.balances[0]?.amount.toFixed()).toBe("1.5"); + }); + + it("projects the generated API amount representation into normalized domain constraints", () => { + const baseYield = yieldApiYieldDtoFixture(); + const yieldModel = yieldApiYieldFixture({ + mechanics: { + ...baseYield.mechanics, + arguments: { + enter: { + fields: [ + { + label: "Amount", + minimum: null, + name: "amount", + type: "string", + }, + ], + }, + }, + }, + }); + + expect(yieldModel.mechanics.arguments?.enter?.fields).toEqual({ + amount: { + maximum: null, + minimum: "0", + required: false, + }, + }); + }); + + it("accepts the Cardano unbounded-maximum amount representation", () => { + const baseYield = yieldApiYieldDtoFixture(); + const yieldModel = yieldApiYieldFixture({ + mechanics: { + ...baseYield.mechanics, + arguments: { + enter: { + fields: [ + { + label: "Amount", + maximum: "-1", + minimum: "5", + name: "amount", + required: false, + type: "string", + }, + ], + }, + }, + }, + }); + + expect(yieldModel.mechanics.arguments?.enter?.fields.amount).toEqual({ + maximum: null, + minimum: "5", + required: false, + }); + }); + + it("accepts string-valued liquidity state from the Yield API", () => { + const yieldModel = yieldApiYieldFixture({ + state: { + liquidityState: { + liquidity: "8045570", + utilization: "0", + }, + }, + }); + + expect(yieldModel.state?.liquidityState).toEqual({ + liquidity: "8045570", + utilization: "0", + }); + }); + + it("keeps only widget-consumed mechanic argument fields", () => { + const baseYield = yieldApiYieldDtoFixture(); + const yieldModel = yieldApiYieldFixture({ + mechanics: { + ...baseYield.mechanics, + arguments: { + enter: { + fields: [ + { + label: "Amount", + maximum: "100", + minimum: "0.000000000000000001", + name: "amount", + required: true, + type: "string", + }, + { + label: "Provider", + name: "providerId", + options: ["provider-a"], + required: true, + type: "string", + }, + { + label: "Resource", + name: "tronResource", + options: ["BANDWIDTH", "ENERGY"], + type: "enum", + }, + { + label: "Validator", + name: "validatorAddress", + required: true, + type: "string", + }, + { + label: "Validators", + name: "validatorAddresses", + type: "string", + }, + { + label: "Subnet", + name: "subnetId", + type: "number", + }, + { + label: "Ignored", + name: "duration", + type: "number", + }, + ], + }, + }, + }, + }); + + expect(yieldModel.mechanics.arguments?.enter?.fields).toEqual({ + amount: { + maximum: "100", + minimum: "0.000000000000000001", + required: true, + }, + providerId: { + options: ["provider-a"], + required: true, + }, + subnetId: { + required: false, + }, + tronResource: { + options: ["BANDWIDTH", "ENERGY"], + required: false, + }, + validatorAddress: { + required: true, + }, + validatorAddresses: { + required: false, + }, + }); + }); + + it("validates unconsumed mechanic fields through the API schema before projection", () => { + const baseYield = yieldApiYieldDtoFixture(); + + expect(() => + yieldApiYieldFixture({ + mechanics: { + ...baseYield.mechanics, + arguments: { + enter: { + fields: [ + { + name: "duration", + type: "number", + } as never, + ], + }, + }, + }, + }) + ).toThrow(); + }); + + it("projects consumed fields from every mechanic argument container", () => { + const baseYield = yieldApiYieldDtoFixture(); + const yieldModel = yieldApiYieldFixture({ + mechanics: { + ...baseYield.mechanics, + arguments: { + balance: { + fields: [ + { + label: "Resource", + name: "tronResource", + options: ["ENERGY"], + type: "enum", + }, + ], + }, + enter: { + fields: [ + { + label: "Amount", + name: "amount", + type: "string", + }, + ], + }, + exit: { + fields: [ + { + label: "Validator", + name: "validatorAddress", + required: true, + type: "string", + }, + ], + }, + manage: { + CLAIM_REWARDS: { + fields: [ + { + label: "Provider", + name: "providerId", + options: ["provider-a"], + type: "string", + }, + ], + }, + }, + }, + }, + }); + + expect(yieldModel.mechanics.arguments).toEqual({ + balance: { + fields: { + tronResource: { + options: ["ENERGY"], + required: false, + }, + }, + }, + enter: { + fields: { + amount: { + maximum: null, + minimum: "0", + required: false, + }, + }, + }, + exit: { + fields: { + validatorAddress: { + required: true, + }, + }, + }, + manage: { + CLAIM_REWARDS: { + fields: { + providerId: { + options: ["provider-a"], + required: false, + }, + }, + }, + }, + }); + }); + + it.each([ + { + field: { + label: "Amount", + name: "amount", + type: "number", + }, + name: "an amount with a non-canonical type", + }, + { + field: { + label: "Amount", + minimum: "not-a-number", + name: "amount", + type: "string", + }, + name: "non-finite amount bounds", + }, + { + field: { + label: "Amount", + maximum: "2", + minimum: "3", + name: "amount", + type: "string", + }, + name: "an amount maximum below its minimum", + }, + { + field: { + label: "Amount", + maximum: null, + minimum: "-1", + name: "amount", + type: "string", + }, + name: "a partial force-max sentinel", + }, + { + field: { + label: "Provider", + name: "providerId", + options: [], + required: true, + type: "string", + }, + name: "a required provider without options", + }, + { + field: { + label: "Provider", + name: "providerId", + options: [""], + type: "string", + }, + name: "an invalid provider option", + }, + { + field: { + label: "Resource", + name: "tronResource", + options: ["COMPUTE"], + type: "enum", + }, + name: "an invalid Tron resource option", + }, + { + field: { + label: "Provider", + name: "providerId", + options: ["provider-a"], + type: "enum", + }, + name: "a provider with a non-canonical type", + }, + { + field: { + label: "Resource", + name: "tronResource", + options: ["ENERGY"], + type: "string", + }, + name: "a Tron resource with a non-canonical type", + }, + { + field: { + label: "Validator", + name: "validatorAddress", + type: "address", + }, + name: "a validator address with a non-canonical type", + }, + { + field: { + label: "Validators", + name: "validatorAddresses", + type: "address", + }, + name: "validator addresses with a non-canonical type", + }, + { + field: { + label: "Subnet", + name: "subnetId", + type: "string", + }, + name: "a subnet ID with a non-canonical type", + }, + ])("rejects $name", ({ field }) => { + const baseYield = yieldApiYieldDtoFixture(); + + expect(() => + yieldApiYieldFixture({ + mechanics: { + ...baseYield.mechanics, + arguments: { + enter: { + fields: [field as never], + }, + }, + }, + }) + ).toThrow(); + }); + + it("rejects a present argument container without a fields array", () => { + const valid = yieldApiYieldDtoFixture(); + + expect(() => + Schema.decodeUnknownSync(EarnYield)({ + ...valid, + mechanics: { + ...valid.mechanics, + arguments: { enter: {} }, + }, + }) + ).toThrow(); + }); +}); diff --git a/packages/widget/tests/domain/evm-chains.test.ts b/packages/widget/tests/domain/evm-chains.test.ts index 31439c0f4..3bf73f33b 100644 --- a/packages/widget/tests/domain/evm-chains.test.ts +++ b/packages/widget/tests/domain/evm-chains.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; import { - EvmChainIds, evmChainsMap, supportedEVMChainsSet, } from "../../src/domain/types/chains/evm"; import { EvmNetworks } from "../../src/domain/types/chains/networks"; +import { EvmChainIds } from "../../src/public-api/types"; describe("EVM chains", () => { it("configures Pharos with PROS as its native gas token", () => { diff --git a/packages/widget/tests/domain/financial-models.test.ts b/packages/widget/tests/domain/financial-models.test.ts new file mode 100644 index 000000000..0b9c10b75 --- /dev/null +++ b/packages/widget/tests/domain/financial-models.test.ts @@ -0,0 +1,96 @@ +import BigNumber from "bignumber.js"; +import { Effect, Logger, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { + GasBalancesCommand, + TokenBalanceScanCommand, + TokenBalancesResponse, + YieldBalancesCommand, +} from "../../src/domain/schema/financial-models"; + +const token = { + name: "Ethereum", + symbol: "ETH", + decimals: 18, + network: "ethereum", + address: "0xAbCd", +} as const; + +const decode = >( + schema: S, + input: unknown +) => + Effect.runPromise( + Schema.decodeUnknownEffect(schema)(input).pipe( + Effect.provide(Logger.layer([])) + ) + ); + +describe("financial API boundary schemas", () => { + it("decodes wallet-scoped balance commands into branded application values", () => { + const tokenCommand = Schema.decodeUnknownSync(TokenBalanceScanCommand)({ + addresses: { address: "0xWallet" }, + network: "ethereum", + }); + const yieldCommand = Schema.decodeUnknownSync(YieldBalancesCommand)({ + queries: [ + { + address: "0xWallet", + network: "ethereum", + yieldId: "ethereum-eth-native-staking", + }, + ], + }); + const gasCommand = Schema.decodeUnknownSync(GasBalancesCommand)({ + addresses: [ + { + address: "0xWallet", + network: "ethereum", + tokenAddress: "0xGasToken", + }, + ], + }); + + expect(tokenCommand.addresses.address).toBe("0xWallet"); + expect(gasCommand.addresses[0]?.tokenAddress).toBe("0xGasToken"); + expect(yieldCommand.queries[0]?.yieldId).toBe( + "ethereum-eth-native-staking" + ); + }); + + it("omits a complete malformed token balance and retains valid siblings", async () => { + const balances = await decode(TokenBalancesResponse, [ + { + amount: "1.5", + availableYields: ["ethereum-eth-native-staking"], + token, + }, + { + amount: "NaN", + availableYields: ["ethereum-eth-native-staking"], + token, + }, + { + amount: "2", + availableYields: ["ethereum-eth-native-staking"], + token: { ...token, decimals: "18" }, + }, + ]); + + expect(balances).toHaveLength(1); + expect(balances[0]?.amount).toBeInstanceOf(BigNumber); + expect(balances[0]?.amount.toFixed()).toBe("1.5"); + }); + + it("returns an empty balance list when every top-level entry is malformed", async () => { + const balances = await decode(TokenBalancesResponse, [ + { + amount: "not-a-decimal", + availableYields: [], + token, + }, + ]); + + expect(balances).toEqual([]); + }); +}); diff --git a/packages/widget/tests/domain/health-price-models.test.ts b/packages/widget/tests/domain/health-price-models.test.ts new file mode 100644 index 000000000..f8c30ff12 --- /dev/null +++ b/packages/widget/tests/domain/health-price-models.test.ts @@ -0,0 +1,61 @@ +import { Effect, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { + HealthStatus, + PriceRequest, + PriceResponse, +} from "../../src/domain/schema/health-price-models"; + +describe("health and price application schemas", () => { + it("strictly decodes health responses", () => { + const health = Schema.decodeUnknownSync(HealthStatus)({ + status: "OK", + timestamp: "2026-07-10T12:00:00.000Z", + }); + + expect(health.status).toBe("OK"); + expect(() => + Schema.decodeUnknownSync(HealthStatus)({ + status: "DEGRADED", + timestamp: "not-a-date", + }) + ).toThrow(); + }); + + it("decodes application price commands from the generated wire shape", () => { + const request = Schema.decodeUnknownSync(PriceRequest)({ + currency: "USD", + tokenList: [ + { + address: "0xToken", + decimals: 18, + name: "Ethereum", + network: "ethereum", + symbol: "ETH", + }, + ], + }); + + expect(request.tokenList[0]?.address).toBe("0xToken"); + }); + + it("omits malformed top-level price entries while retaining valid siblings", async () => { + const prices = await Effect.runPromise( + Schema.decodeUnknownEffect(PriceResponse)({ + "ethereum-": { price: 3000, price_24_h: 2900 }, + "cosmos-": { price: "invalid", price_24_h: 5 }, + "solana-": { price: 150 }, + }) + ); + + expect(prices.value.get("ethereum-")).toEqual({ + price: 3000, + price24H: 2900, + }); + expect(prices.value.has("cosmos-")).toBe(false); + expect(prices.value.get("solana-")).toEqual({ + price: 150, + price24H: undefined, + }); + }); +}); diff --git a/packages/widget/tests/domain/kyc.test.ts b/packages/widget/tests/domain/kyc.test.ts index f6ca6aaf8..59651471f 100644 --- a/packages/widget/tests/domain/kyc.test.ts +++ b/packages/widget/tests/domain/kyc.test.ts @@ -1,10 +1,11 @@ import { describe, expect, it } from "vitest"; +import type { EarnYieldWithProvider } from "../../src/domain/schema/earn-models"; import { getKycProviderName, getKycUrl, mapKycStatusToGate, } from "../../src/domain/types/kyc"; -import type { Yield } from "../../src/domain/types/yields"; + import { yieldApiProviderFixture, yieldApiYieldFixture } from "../fixtures"; const kycEligibility = { @@ -17,7 +18,9 @@ const kycEligibility = { subjectTypes: ["KYC"], } as const; -const createYield = (overrides?: Partial): Yield => +const createYield = ( + overrides?: Partial +): EarnYieldWithProvider => ({ ...yieldApiYieldFixture(), provider: yieldApiProviderFixture({ @@ -25,7 +28,7 @@ const createYield = (overrides?: Partial): Yield => website: "https://superstate.com", }), ...overrides, - }) as Yield; + }) as EarnYieldWithProvider; describe("KYC gate mapping", () => { it("allows approved and not required statuses", () => { diff --git a/packages/widget/tests/domain/position-derivations.test.ts b/packages/widget/tests/domain/position-derivations.test.ts new file mode 100644 index 000000000..74247e2ea --- /dev/null +++ b/packages/widget/tests/domain/position-derivations.test.ts @@ -0,0 +1,173 @@ +import { Option, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import type * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { EarnPosition } from "../../src/domain/schema/earn-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + getPositionBalances, + getPositionData, + toPositionBalancesByType, + toPositionsData, +} from "../../src/domain/types/positions"; +import { + PositionBalancesKey, + PositionDataKey, + positionBalancesAtom, + positionDataAtom, + toPositionItems, +} from "../../src/features/portfolio/resources/positions"; +import { yieldPositionsResourceAtom } from "../../src/resources/yield-positions/yield-positions"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture, yieldBalanceFixture } from "../fixtures"; + +const makePosition = () => { + const yieldDto = yieldApiYieldFixture(); + + return Schema.decodeUnknownSync(EarnPosition)({ + balances: [ + yieldBalanceFixture({ + amount: "2", + amountUsd: "5", + type: "active", + token: yieldDto.token, + }), + yieldBalanceFixture({ + amount: "0", + amountUsd: "0", + type: "claimable", + token: yieldDto.token, + }), + ], + outputTokenBalance: null, + yieldId: yieldDto.id, + }); +}; + +describe("position derivations", () => { + it("normalizes positions and selects a requested or fallback balance group", () => { + const position = makePosition(); + const positions = toPositionsData([position]); + const selected = getPositionData(positions, position.yieldId); + const balances = getPositionBalances(selected, "missing-balance-group"); + + expect(selected?.yieldId).toBe(position.yieldId); + expect(balances?.balances).toHaveLength(2); + expect(balances?.type).toBe("default"); + }); + + it("groups non-zero balances and projects visible table rows", () => { + const position = makePosition(); + const positions = toPositionsData([position]); + const selected = getPositionBalances( + getPositionData(positions, position.yieldId), + "default" + ); + const byType = toPositionBalancesByType(selected?.balances ?? []); + const rows = toPositionItems(positions, false); + + expect(byType.get("active")?.[0]?.tokenPriceInUsd.toFixed()).toBe("5"); + expect(byType.has("claimable")).toBe(false); + expect(rows).toHaveLength(1); + expect(rows[0]?.balancesWithAmount).toHaveLength(1); + }); + + it("deduplicates derived atom families by value-equal domain keys", () => { + const { yieldId } = makePosition(); + const scope = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ), + network: "ethereum", + }); + + expect(positionDataAtom(new PositionDataKey({ scope, yieldId }))).toBe( + positionDataAtom(new PositionDataKey({ scope, yieldId })) + ); + }); + + it("resolves position balances for an explicit wallet scope", () => { + const position = makePosition(); + const scope = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ), + network: "ethereum", + }); + const resource = yieldPositionsResourceAtom(scope); + const balances = positionBalancesAtom( + new PositionBalancesKey({ + balanceId: "default", + scope, + yieldId: position.yieldId, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + [resource, AsyncResult.success({ errors: [], items: [position] })], + ], + }); + + expect( + Option.getOrNull(AsyncResult.value(registry.get(balances)))?.balances + ).toHaveLength(2); + }); + + it("retains same-wallet position data only while revalidating and clears it across owners or successful absence", () => { + const position = makePosition(); + const scopeA = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ), + network: "ethereum", + }); + const scopeB = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" + ), + network: "ethereum", + }); + const response = { errors: [], items: [position] }; + const resourceA = yieldPositionsResourceAtom(scopeA); + const resourceB = yieldPositionsResourceAtom(scopeB); + const selectedA = positionDataAtom( + new PositionDataKey({ scope: scopeA, yieldId: position.yieldId }) + ); + const selectedB = positionDataAtom( + new PositionDataKey({ scope: scopeB, yieldId: position.yieldId }) + ); + + const readA = (result: Atom.Type) => + AtomRegistry.make({ initialValues: [[resourceA, result]] }).get( + selectedA + ); + const readB = (result: Atom.Type) => + AtomRegistry.make({ initialValues: [[resourceB, result]] }).get( + selectedB + ); + + expect( + Option.getOrNull(AsyncResult.value(readA(AsyncResult.success(response)))) + ).not.toBeNull(); + + expect( + Option.getOrNull( + AsyncResult.value( + readA(AsyncResult.waiting(AsyncResult.success(response))) + ) + ) + ).not.toBeNull(); + + expect( + Option.getOrNull( + AsyncResult.value(readB(AsyncResult.success({ errors: [], items: [] }))) + ) + ).toBeNull(); + expect( + Option.getOrNull( + AsyncResult.value(readA(AsyncResult.success({ errors: [], items: [] }))) + ) + ).toBeNull(); + }); +}); diff --git a/packages/widget/tests/domain/reward-rate.test.ts b/packages/widget/tests/domain/reward-rate.test.ts index 7c9e32026..85d35c823 100644 --- a/packages/widget/tests/domain/reward-rate.test.ts +++ b/packages/widget/tests/domain/reward-rate.test.ts @@ -2,17 +2,20 @@ import { describe, expect, it } from "vitest"; import { getEffectiveYieldRewardRateDetails, getRewardRateBreakdown, + type YieldRewardRateDto, } from "../../src/domain/types/reward-rate"; -import type { RewardDto } from "../../src/generated/api/yield"; import { yieldApiValidatorFixture, yieldApiYieldFixture, yieldRewardRateFixture, } from "../fixtures"; +import { decodeValidator } from "../utils/validators"; const token = yieldApiYieldFixture().token; -const nativeComponent = (rate: number): RewardDto => ({ +const nativeComponent = ( + rate: number +): YieldRewardRateDto["components"][number] => ({ rate, rateType: "APY", token, @@ -32,20 +35,22 @@ describe("getEffectiveYieldRewardRateDetails", () => { selectedValidators: new Map(), yieldDto, }) - ).toBe(rewardRate); + ).toBe(yieldDto.rewardRate); }); it("uses the selected validator reward rate", () => { const yieldDto = yieldApiYieldFixture({ rewardRate: yieldRewardRateFixture({ total: 0.1539 }), }); - const validator = yieldApiValidatorFixture({ - address: "validator-1", - rewardRate: yieldRewardRateFixture({ - total: 0.1582, - components: [nativeComponent(0.1582)], - }), - }); + const validator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + rewardRate: yieldRewardRateFixture({ + total: 0.1582, + components: [nativeComponent(0.1582)], + }), + }) + ); const rewardRate = getEffectiveYieldRewardRateDetails({ selectedValidators: new Map([[validator.address, validator]]), @@ -60,20 +65,24 @@ describe("getEffectiveYieldRewardRateDetails", () => { const yieldDto = yieldApiYieldFixture({ rewardRate: yieldRewardRateFixture({ total: 0.1539 }), }); - const firstValidator = yieldApiValidatorFixture({ - address: "validator-1", - rewardRate: yieldRewardRateFixture({ - total: 0.16, - components: [nativeComponent(0.16)], - }), - }); - const secondValidator = yieldApiValidatorFixture({ - address: "validator-2", - rewardRate: yieldRewardRateFixture({ - total: 0.18, - components: [nativeComponent(0.18)], - }), - }); + const firstValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + rewardRate: yieldRewardRateFixture({ + total: 0.16, + components: [nativeComponent(0.16)], + }), + }) + ); + const secondValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-2", + rewardRate: yieldRewardRateFixture({ + total: 0.18, + components: [nativeComponent(0.18)], + }), + }) + ); const rewardRate = getEffectiveYieldRewardRateDetails({ selectedValidators: new Map([ diff --git a/packages/widget/tests/domain/yield-summaries.test.ts b/packages/widget/tests/domain/yield-summaries.test.ts index ae40c3ff7..0f145d2ba 100644 --- a/packages/widget/tests/domain/yield-summaries.test.ts +++ b/packages/widget/tests/domain/yield-summaries.test.ts @@ -1,151 +1,25 @@ -import { describe, expect, it, vi } from "vitest"; -import { - fetchYieldSummariesByIds, - isVisibleYieldSummary, - type YieldSummary, -} from "../../src/hooks/api/use-yield-summaries"; -import type { ApiClient } from "../../src/providers/api/api-client"; -import { yieldApiYieldFixture, yieldRewardRateFixture } from "../fixtures"; - -const summary = (overrides?: Parameters[0]) => - yieldApiYieldFixture(overrides) as YieldSummary; - -describe("isVisibleYieldSummary", () => { - it("includes enterable, supported-chain, non-zero-reward summaries", () => { - expect(isVisibleYieldSummary(summary())).toBe(true); - }); - - it("excludes summaries that are not enterable", () => { - expect( - isVisibleYieldSummary(summary({ status: { enter: false, exit: true } })) - ).toBe(false); - }); - - it("excludes summaries with a zero reward rate", () => { - expect( - isVisibleYieldSummary( - summary({ rewardRate: yieldRewardRateFixture({ total: 0 }) }) - ) - ).toBe(false); - }); - - it("includes whitelisted zero-reward summaries", () => { - expect( - isVisibleYieldSummary( - summary({ - id: "optimism-usdc-gtusdcb-0x4ffc4e5f1f1f5c43dc9bc27b53728da13b02be35-4626-vault", - token: { - name: "USD Coin", - symbol: "USDC", - decimals: 6, - network: "optimism", - }, - rewardRate: yieldRewardRateFixture({ total: 0 }), - }) - ) - ).toBe(true); - }); -}); - -describe("fetchYieldSummariesByIds", () => { - it("splits yield IDs into bounded chunks", async () => { - const items = Array.from({ length: 5 }, (_, index) => - summary({ id: `yield-${index}` }) - ); - - const getYields = vi.fn( - async ({ params }: { params: { yieldIds: ReadonlyArray } }) => ({ - total: params.yieldIds.length, - offset: 0, - limit: params.yieldIds.length, - items: params.yieldIds.flatMap( - (yieldId) => items.find((item) => item.id === yieldId) ?? [] - ), - }) - ); - - const apiClient = { - withOptions: () => ({ yield: { YieldsControllerGetYields: getYields } }), - } as unknown as ApiClient; - - const result = await fetchYieldSummariesByIds({ - apiClient, - chunkSize: 2, - yieldIds: items.map((item) => item.id), - }); - - expect(result.map((item) => item.id)).toEqual([ - "yield-0", - "yield-1", - "yield-2", - "yield-3", - "yield-4", - ]); - expect(getYields).toHaveBeenCalledTimes(3); - expect(getYields.mock.calls.map(([arg]) => arg.params.yieldIds)).toEqual([ - ["yield-0", "yield-1"], - ["yield-2", "yield-3"], - ["yield-4"], - ]); - }); - - it("uses a single request when IDs fit within the chunk size", async () => { - const getYields = vi.fn(async () => ({ - total: 2, - offset: 0, - limit: 2, - items: [summary({ id: "yield-0" }), summary({ id: "yield-1" })], - })); - - const apiClient = { - withOptions: () => ({ yield: { YieldsControllerGetYields: getYields } }), - } as unknown as ApiClient; - - await fetchYieldSummariesByIds({ - apiClient, - chunkSize: 2, - yieldIds: ["yield-0", "yield-1"], - }); - - expect(getYields).toHaveBeenCalledTimes(1); - expect(getYields).toHaveBeenCalledWith({ - params: { - yieldIds: ["yield-0", "yield-1"], +import { Effect, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { EarnYieldPage } from "../../src/domain/schema/earn-models"; +import { yieldApiYieldDtoFixture } from "../fixtures"; + +describe("yield summary atom boundary", () => { + it("retains valid summaries when a top-level sibling is malformed", async () => { + const valid = yieldApiYieldDtoFixture({ id: "yield-valid", prime: false }); + const invalid: Record = { + ...yieldApiYieldDtoFixture({ id: "yield-invalid", prime: false }), + token: { ...valid.token, decimals: "invalid" }, + }; + const page = await Effect.runPromise( + Schema.decodeUnknownEffect(EarnYieldPage)({ + items: [valid, invalid], limit: 2, - }, - }); - }); - - it("deduplicates requested IDs and returns summaries in first occurrence order", async () => { - const getYields = vi.fn( - async ({ params }: { params: { yieldIds: ReadonlyArray } }) => ({ - total: params.yieldIds.length, offset: 0, - limit: params.yieldIds.length, - items: [...params.yieldIds] - .reverse() - .map((yieldId) => summary({ id: yieldId })), + total: 2, }) ); - const apiClient = { - withOptions: () => ({ yield: { YieldsControllerGetYields: getYields } }), - } as unknown as ApiClient; - - const result = await fetchYieldSummariesByIds({ - apiClient, - chunkSize: 2, - yieldIds: ["yield-2", "yield-1", "yield-2", "yield-0"], - }); - - expect(result.map((item) => item.id)).toEqual([ - "yield-2", - "yield-1", - "yield-0", - ]); - expect(getYields.mock.calls.map(([arg]) => arg.params.yieldIds)).toEqual([ - ["yield-2", "yield-1"], - ["yield-0"], - ]); + expect(page.items?.map((item) => item.id)).toEqual(["yield-valid"]); + expect(page.total).toBe(2); }); }); diff --git a/packages/widget/tests/features/action-history.test.ts b/packages/widget/tests/features/action-history.test.ts new file mode 100644 index 000000000..d4f276659 --- /dev/null +++ b/packages/widget/tests/features/action-history.test.ts @@ -0,0 +1,24 @@ +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { + actionHistoryRevisionAtom, + incrementActionHistoryRevision, + resetActionHistoryRevision, +} from "../../src/features/classic-transaction-flow/state/action-history"; + +describe("action history atoms", () => { + it("initializes, updates, and resets explicitly", () => { + const registry = AtomRegistry.make(); + + expect(registry.get(actionHistoryRevisionAtom)).toBe(0); + + registry.set( + actionHistoryRevisionAtom, + incrementActionHistoryRevision(registry.get(actionHistoryRevisionAtom)) + ); + expect(registry.get(actionHistoryRevisionAtom)).toBe(1); + + registry.set(actionHistoryRevisionAtom, resetActionHistoryRevision()); + expect(registry.get(actionHistoryRevisionAtom)).toBe(0); + }); +}); diff --git a/packages/widget/tests/features/activity-resume-action.test.ts b/packages/widget/tests/features/activity-resume-action.test.ts new file mode 100644 index 000000000..0019d106e --- /dev/null +++ b/packages/widget/tests/features/activity-resume-action.test.ts @@ -0,0 +1,140 @@ +import { Effect, Layer, Schema } from "effect"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { resumeActivityActionAtom } from "../../src/features/activity/state/resume-action"; +import { classicFlowSessionStore } from "../../src/features/classic-transaction-flow/facade"; +import { walletConnectionStateAtom } from "../../src/features/wallet/public-state"; +import { + WidgetNavigation, + type WidgetNavigationOptions, + type WidgetPath, +} from "../../src/services/navigation/widget-navigation"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { + yieldApiActionFixture, + yieldApiTransactionFixture, + yieldApiYieldFixture, +} from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x1234567890123456789012345678901234567890" +); +const walletScope = new WalletScopeKey({ + address, + network: "ethereum", +}); +const connectedWallet = { + additionalAddresses: null, + address, + chain: {} as never, + connector: {} as never, + connectorChains: [], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum" as const, + status: "connected" as const, +}; + +const makeRegistry = ( + push: (path: WidgetPath, options?: WidgetNavigationOptions) => void +) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + WidgetNavigation, + WidgetNavigation.of({ + back: () => Effect.void, + push: (path, options) => Effect.sync(() => push(path, options)), + replace: () => Effect.void, + }) + ) + ), + Atom.initialValue(walletConnectionStateAtom, connectedWallet), + ], + }); + +describe("Activity resume action", () => { + it("starts the Flow Session and navigates a resumable action to Review", async () => { + const push = vi.fn(); + const registry = makeRegistry(push); + const selectedYield = yieldApiYieldFixture(); + const action = yieldApiActionFixture({ + status: "CREATED", + yieldId: selectedYield.id, + }); + + try { + registry.set(resumeActivityActionAtom, { + action, + providersDetails: [], + selectionMode: "navigate", + validators: [], + walletScope, + yield: selectedYield, + }); + + await vi.waitFor(() => expect(push).toHaveBeenCalledOnce()); + expect(push).toHaveBeenCalledWith("/activity/review", { + _tag: "Push", + path: "/activity/review", + }); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom)?.intake + ).toMatchObject({ + _tag: "ActivityResume", + action: { id: action.id }, + }); + } finally { + registry.dispose(); + } + }); + + it("navigates a completed action with explorer state from the Atom command", async () => { + const push = vi.fn(); + const registry = makeRegistry(push); + const selectedYield = yieldApiYieldFixture(); + const transaction = yieldApiTransactionFixture({ + explorerUrl: "https://explorer.example/transaction", + type: "STAKE", + }); + + try { + registry.set(resumeActivityActionAtom, { + action: yieldApiActionFixture({ + status: "SUCCESS", + transactions: [transaction], + type: "STAKE", + yieldId: selectedYield.id, + }), + providersDetails: [], + selectionMode: "navigate", + validators: [], + walletScope, + yield: selectedYield, + }); + + await vi.waitFor(() => expect(push).toHaveBeenCalledOnce()); + expect(push).toHaveBeenCalledWith( + "/activity/stake-review/complete", + expect.objectContaining({ + state: { + urls: [ + { + type: transaction.type, + url: transaction.explorerUrl, + }, + ], + }, + }) + ); + } finally { + registry.dispose(); + } + }); +}); diff --git a/packages/widget/tests/features/activity-workflow.dom.test.tsx b/packages/widget/tests/features/activity-workflow.dom.test.tsx new file mode 100644 index 000000000..af83bd7f1 --- /dev/null +++ b/packages/widget/tests/features/activity-workflow.dom.test.tsx @@ -0,0 +1,96 @@ +import { act, type ReactNode } from "react"; +import { I18nextProvider } from "react-i18next"; +import { MemoryRouter } from "react-router"; +import { describe, expect, it, vi } from "vitest"; +import { SKAtomRegistryProvider } from "../../src/app/composition/providers/atom-runtime"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import type { ActivityFilterOption } from "../../src/features/activity/model/filters"; +import { useActivityFilter } from "../../src/features/activity/react/use-activity-filter"; +import { CompletePageComponent } from "../../src/features/classic-transaction-flow/ui/complete/pages/common.page"; +import { i18nInstance } from "../../src/translation"; +import { render } from "../utils/test-utils.dom"; + +const settings = normalizeWidgetConfig({ + apiKey: "test-api-key", + variant: "default", +}); + +const ActivityFilterHarness = ({ + options, +}: { + readonly options: ReadonlyArray; +}) => { + const { selectedFilter, setSelectedFilter } = useActivityFilter(options); + + return ( + <> + {selectedFilter} + + + ); +}; + +const wrap = (children: ReactNode) => ( + + + {children} + + +); + +describe("activity and completion workflows", () => { + it("stores the selected activity filter in the feature atom and falls back when unavailable", async () => { + const options: ActivityFilterOption[] = [{ count: 1, filter: "defi" }]; + const app = await render(wrap()); + + expect(app.container.querySelector("output")?.textContent).toBe("all"); + + await act(async () => { + app.container.querySelector("button")?.click(); + }); + + expect(app.container.querySelector("output")?.textContent).toBe("defi"); + + await app.rerender(wrap()); + + expect(app.container.querySelector("output")?.textContent).toBe("all"); + }); + + it("renders completion transaction actions from explicit workflow input", async () => { + const onViewTransactionClick = vi.fn(); + const app = await render( + wrap( + + ) + ); + const transactionButton = app.container.querySelector( + 'button:not([data-rk^="footer-button"])' + ); + + expect(transactionButton?.textContent).toContain("View Stake transaction"); + + await act(async () => transactionButton?.click()); + + expect(onViewTransactionClick).toHaveBeenCalledWith( + "https://explorer.test/tx" + ); + }); +}); diff --git a/packages/widget/tests/features/borrow-execution-state.test.ts b/packages/widget/tests/features/borrow-execution-state.test.ts new file mode 100644 index 000000000..27c4ae90f --- /dev/null +++ b/packages/widget/tests/features/borrow-execution-state.test.ts @@ -0,0 +1,105 @@ +import { Schema } from "effect"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { borrowActionFormAtom } from "../../src/features/borrow/atoms/action-form"; +import { borrowTransactionFlowOutcomeBindingAtom } from "../../src/features/borrow/atoms/transaction-flow-outcomes"; +import type { + BorrowTransactionFlowIntake, + BorrowTransactionFlowReview, +} from "../../src/features/borrow-transaction-flow/state"; +import { makeBorrowFlowSessionStore } from "../../src/features/borrow-transaction-flow/state/borrow-flow-session-store"; +import { publishBorrowTransactionFlowOutcomeAtom } from "../../src/features/borrow-transaction-flow/state/outcomes"; +import { currentWalletScopeAtom } from "../../src/features/wallet/state/selectors"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; + +describe("borrow flow session state", () => { + it("captures immutable, fresh sessions and ignores a stale clear", () => { + const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ); + const walletScope = new WalletScopeKey({ address, network: "base" }); + const registry = AtomRegistry.make({ + initialValues: [[currentWalletScopeAtom, walletScope]], + }); + const store = makeBorrowFlowSessionStore(); + const summary = { + action: "borrow", + marketLabel: "USDC market", + network: "base", + providerName: "Provider", + } as const; + const intake = { + entry: { _tag: "BorrowDashboard" }, + request: { + action: "borrow", + address, + args: { marketId: "market-1" }, + integrationId: "provider-1", + }, + summary, + } as BorrowTransactionFlowIntake; + + registry.set(store.startAtom, intake); + const first = registry.get(store.currentSessionAtom); + registry.set(store.startAtom, intake); + const second = registry.get(store.currentSessionAtom); + + expect(first?.epoch).toBe(1); + expect(second?.epoch).toBe(2); + expect(second?.intake).not.toBe(intake); + expect(second?.walletScope).not.toBe(walletScope); + + registry.set(store.clearAtom, first?.epoch ?? 0); + expect(registry.get(store.currentSessionAtom)?.epoch).toBe(2); + }); + + it("applies each flow outcome once to Borrow-owned form state", () => { + const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ); + const review = { + request: { + action: "borrow", + address, + args: { marketId: "market-1" }, + integrationId: "provider-1", + }, + summary: { + action: "borrow", + marketLabel: "USDC market", + network: "base", + providerName: "Provider", + }, + } as BorrowTransactionFlowReview; + const registry = AtomRegistry.make(); + const unmount = registry.mount(borrowTransactionFlowOutcomeBindingAtom); + + registry.set(borrowActionFormAtom, { + type: "prepareReview", + reviewState: review, + }); + registry.set(publishBorrowTransactionFlowOutcomeAtom, { + _tag: "ExecutionStarted", + epoch: 1, + }); + expect(registry.get(borrowActionFormAtom).type).toBe("idle"); + + registry.set(borrowActionFormAtom, { + type: "prepareReview", + reviewState: review, + }); + registry.set(publishBorrowTransactionFlowOutcomeAtom, { + _tag: "ExecutionStarted", + epoch: 1, + }); + expect(registry.get(borrowActionFormAtom).type).toBe("review"); + + registry.set(publishBorrowTransactionFlowOutcomeAtom, { + _tag: "Done", + epoch: 1, + }); + expect(registry.get(borrowActionFormAtom).type).toBe("idle"); + unmount(); + }); +}); diff --git a/packages/widget/tests/features/classic-flow-navigation.dom.test.tsx b/packages/widget/tests/features/classic-flow-navigation.dom.test.tsx new file mode 100644 index 000000000..d4163c2ba --- /dev/null +++ b/packages/widget/tests/features/classic-flow-navigation.dom.test.tsx @@ -0,0 +1,526 @@ +import { RegistryProvider, useAtomSet, useAtomValue } from "@effect/atom-react"; +import { Layer, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { HttpResponse, http } from "msw"; +import { act } from "react"; +import { + Route, + RouterProvider, + Routes, + useLocation, + useNavigate, +} from "react-router"; +import { ApplicationRouteContentProvider } from "../../src/app/composition/application-route-content"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import { + applicationRouterAtom, + applicationRouterRuntime, +} from "../../src/app/runtime/application-router-runtime"; +import { ActionCommand } from "../../src/domain/schema/action-models"; +import { + classicFlowSessionStore, + makeStartClassicFlowSession, +} from "../../src/features/classic-transaction-flow/facade"; +import type { ClassicTransactionFlowIntake } from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { + ClassicFlowExecutionScope, + ClassicFlowReviewScope, + EnterClassicFlowRoute, + useClassicFlowExecution, + useClassicFlowReview, + useClassicFlowSession, +} from "../../src/features/classic-transaction-flow/react/classic-flow-route"; +import { WalletScopeRoute } from "../../src/features/wallet/react/wallet-scope-route"; +import { ApplicationRouter } from "../../src/services/navigation/application-router"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import type { NormalizedWalletState } from "../../src/services/wallet/domain/state"; +import { disconnectedNormalizedWalletState } from "../../src/services/wallet/domain/state"; +import { yieldApiActionFixture, yieldApiYieldFixture } from "../fixtures"; +import { describe, expect, it, vi } from "../utils/test-extend.dom"; +import { render } from "../utils/test-utils.dom"; + +const yieldApiUrl = "https://yield.example.com"; +const legacyApiUrl = "https://api.example.com"; +const command = Schema.decodeUnknownSync(ActionCommand)({ + address: "0xWallet", + yieldId: "ethereum-eth-native-staking", +}); +const selectedStake = yieldApiYieldFixture({ id: command.yieldId }); +const walletScope = new WalletScopeKey({ + address: command.address, + network: "ethereum", +}); +const connectedWalletState: NormalizedWalletState = { + additionalAddresses: null, + address: command.address, + chain: {} as never, + connector: {} as never, + connectorChains: [], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum", + status: "connected", +}; + +const intake: ClassicTransactionFlowIntake = { + _tag: "Enter", + gasFeeToken: selectedStake.mechanics.gasFeeToken, + providersDetails: [], + request: command, + selectedStake, + selectedToken: selectedStake.token, + selectedValidators: new Map(), + walletScope, +}; + +const StartPage = () => { + const session = useAtomValue(classicFlowSessionStore.currentSessionAtom); + const start = useAtomSet(classicFlowSessionStore.startAtom); + const navigate = useNavigate(); + + return ( + <> + + + + + ); +}; + +const ReviewPage = () => { + useClassicFlowSession(); + const reviewFacade = useClassicFlowReview(); + const navigate = useNavigate(); + const review = useAtomValue(reviewFacade.reviewViewAtom); + const confirm = useAtomSet(reviewFacade.confirmAtom); + const start = useAtomSet(classicFlowSessionStore.startAtom); + + return ( + <> + present + {review.action?.id} + + {review.prices ? "ready" : "loading"} + + + + + + ); +}; + +const StepsPage = () => { + useClassicFlowSession(); + const execution = useClassicFlowExecution(); + const action = useAtomValue(execution.actionAtom); + const back = useAtomSet(execution.backAtom); + const navigate = useNavigate(); + + return ( + <> + present + {action.id} + + + + + + ); +}; + +const CompletePage = () => { + const execution = useClassicFlowExecution(); + const action = useAtomValue(execution.actionAtom); + const navigate = useNavigate(); + + return ( + <> + {action.id} + + + ); +}; + +const FlowRoutes = ({ + walletState, +}: { + readonly walletState: NormalizedWalletState; +}) => { + const location = useLocation(); + const session = useAtomValue(classicFlowSessionStore.currentSessionAtom); + const key = + session && /^\/(?:review|steps|complete)$/.test(location.pathname) + ? "flow-session" + : location.key; + + return ( + + } /> + + } + > + }> + + + + } + /> + }> + } /> + } /> + + + + + ); +}; + +const FlowTestApp = ({ + initialPath = "/", + walletState = connectedWalletState, +}: { + readonly initialPath?: string; + readonly walletState?: NormalizedWalletState; +}) => { + const settings = normalizeWidgetConfig({ + apiKey: "test-key", + baseUrl: legacyApiUrl, + variant: "default", + yieldsApiUrl: yieldApiUrl, + }); + return ( + + + + ); +}; + +const FlowRouter = ({ + walletState, +}: { + readonly walletState: NormalizedWalletState; +}) => { + const router = useAtomValue(applicationRouterAtom); + + return ( + } + > + + + ); +}; + +describe("Classic Transaction Flow navigation", () => { + it("redirects routes with missing intake or execution action", async () => { + const directReview = await render(); + await vi.waitFor(() => + expect(directReview.container.textContent).toContain("Start") + ); + directReview.unmount(); + + const directSteps = await render(); + const button = (label: string) => { + const match = [ + ...directSteps.container.querySelectorAll("button"), + ].find((candidate) => candidate.textContent === label); + if (!match) throw new Error(`Expected ${label} button`); + return match; + }; + await act(async () => button("Start").click()); + await vi.waitFor(() => expect(button("Steps").disabled).toBe(false)); + await act(async () => button("Steps").click()); + await vi.waitFor(() => expect(button("Review").disabled).toBe(true)); + }); + + it("remounts the Flow Session boundary for a replacement snapshot", async ({ + worker, + }) => { + let actionPreviewCalls = 0; + worker.use( + http.post(`${legacyApiUrl}/v1/tokens/prices`, () => + HttpResponse.json({ + "ethereum-": { price: 1, price_24_h: 0 }, + }) + ), + http.post(`${yieldApiUrl}/v1/actions/enter`, () => { + actionPreviewCalls += 1; + return HttpResponse.json( + yieldApiActionFixture({ id: `action-${actionPreviewCalls}` }) + ); + }) + ); + + const app = await render(); + const getButton = (label: string) => { + const button = [ + ...app.container.querySelectorAll("button"), + ].find((candidate) => candidate.textContent === label); + if (!button) throw new Error(`Expected ${label} button`); + return button; + }; + + await act(async () => getButton("Start").click()); + await vi.waitFor(() => expect(getButton("Review").disabled).toBe(false)); + await act(async () => getButton("Review").click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="review-action"]') + ?.textContent + ).toBe("action-1") + ); + + await act(async () => getButton("Replace Session").click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="review-action"]') + ?.textContent + ).toBe("action-2") + ); + + await act(async () => getButton("Confirm").click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="steps-session"]') + ).not.toBeNull() + ); + expect(actionPreviewCalls).toBe(2); + }); + + it("keeps the session and prepares a fresh action after Back", async ({ + worker, + }) => { + let actionPreviewCalls = 0; + let priceCalls = 0; + worker.use( + http.post(`${legacyApiUrl}/v1/tokens/prices`, () => { + priceCalls += 1; + return HttpResponse.json({ + "ethereum-": { price: 1, price_24_h: 0 }, + }); + }), + http.post(`${yieldApiUrl}/v1/actions/enter`, () => { + actionPreviewCalls += 1; + return HttpResponse.json( + yieldApiActionFixture({ id: `action-${actionPreviewCalls}` }) + ); + }) + ); + + const app = await render(); + + const buttons = () => [ + ...app.container.querySelectorAll("button"), + ]; + await act(async () => buttons()[0]?.click()); + await vi.waitFor(() => expect(buttons()[1]?.disabled).toBe(false)); + await act(async () => buttons()[1]?.click()); + await vi.waitFor(() => expect(buttons()[0]?.disabled).toBe(false)); + expect(actionPreviewCalls).toBe(1); + await vi.waitFor(() => expect(priceCalls).toBe(1)); + + const sessionMarker = app.container.querySelector( + '[data-testid="review-session"]' + )?.textContent; + await act(async () => buttons()[0]?.click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="steps-session"]') + ?.textContent + ).toBe(sessionMarker) + ); + + await act(async () => buttons()[0]?.click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="review-session"]') + ?.textContent + ).toBe(sessionMarker) + ); + + expect(priceCalls).toBe(1); + await vi.waitFor(() => expect(actionPreviewCalls).toBe(2)); + await vi.waitFor(() => expect(buttons()[0]?.disabled).toBe(false)); + + await act(async () => buttons()[0]?.click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="steps-session"]') + ?.textContent + ).toBe(sessionMarker) + ); + + await act(async () => buttons()[3]?.click()); + await vi.waitFor(() => expect(actionPreviewCalls).toBe(3)); + expect( + app.container.querySelector('[data-testid="review-session"]')?.textContent + ).toBe(sessionMarker); + + await act(async () => buttons()[0]?.click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="steps-session"]') + ?.textContent + ).toBe(sessionMarker) + ); + await act(async () => buttons()[1]?.click()); + await vi.waitFor(() => expect(actionPreviewCalls).toBe(4)); + expect( + app.container.querySelector('[data-testid="review-session"]')?.textContent + ).toBe(sessionMarker); + + await act(async () => buttons()[1]?.click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="review-session"]') + ).toBeNull() + ); + expect( + app.container.querySelector('[data-testid="steps-session"]') + ).toBeNull(); + }); + + it("keeps one Execution scope across Steps and Complete", async ({ + worker, + }) => { + worker.use( + http.post(`${legacyApiUrl}/v1/tokens/prices`, () => + HttpResponse.json({ + "ethereum-": { price: 1, price_24_h: 0 }, + }) + ), + http.post(`${yieldApiUrl}/v1/actions/enter`, () => + HttpResponse.json(yieldApiActionFixture({ id: "execution-action" })) + ) + ); + const app = await render(); + const button = (label: string) => { + const match = [ + ...app.container.querySelectorAll("button"), + ].find((candidate) => candidate.textContent === label); + if (!match) throw new Error(`Expected ${label} button`); + return match; + }; + + await act(async () => button("Start").click()); + await vi.waitFor(() => expect(button("Review").disabled).toBe(false)); + await act(async () => button("Review").click()); + await vi.waitFor(() => expect(button("Confirm").disabled).toBe(false)); + await act(async () => button("Confirm").click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="steps-action"]')?.textContent + ).toBe("execution-action") + ); + + await act(async () => button("Complete").click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="complete-action"]') + ?.textContent + ).toBe("execution-action") + ); + await act(async () => button("Back to Steps").click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="steps-action"]')?.textContent + ).toBe("execution-action") + ); + }); + + it("ejects and disposes the session when the wallet disconnects", async ({ + worker, + }) => { + worker.use( + http.post(`${yieldApiUrl}/v1/actions/enter`, () => + HttpResponse.json(yieldApiActionFixture()) + ) + ); + const app = await render(); + const buttons = () => [ + ...app.container.querySelectorAll("button"), + ]; + + await act(async () => buttons()[0]?.click()); + await vi.waitFor(() => expect(buttons()[1]?.disabled).toBe(false)); + await act(async () => buttons()[1]?.click()); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="review-session"]') + ).not.toBeNull() + ); + + await app.rerender( + + ); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="review-session"]') + ).toBeNull() + ); + await vi.waitFor(() => expect(buttons()[1]?.disabled).toBe(true)); + }); +}); diff --git a/packages/widget/tests/features/classic-flow-session-facade.test.ts b/packages/widget/tests/features/classic-flow-session-facade.test.ts new file mode 100644 index 000000000..92a73b55c --- /dev/null +++ b/packages/widget/tests/features/classic-flow-session-facade.test.ts @@ -0,0 +1,566 @@ +import BigNumber from "bignumber.js"; +import { DateTime, Duration, Effect, Layer, Schema, Stream } from "effect"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import type { + ActionCommand, + YieldAction, +} from "../../src/domain/schema/action-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + classicFlowSessionStore, + makeStartClassicFlowSession, +} from "../../src/features/classic-transaction-flow/facade"; +import type { ClassicTransactionFlowIntake } from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { + makeClassicFlowExecutionScope, + makeClassicFlowReviewScope, + makeClassicFlowSessionModule, +} from "../../src/features/classic-transaction-flow/state/classic-flow-session-facade"; +import { + type ActionPreviewRequest, + YieldOperations, +} from "../../src/services/api/yield-operations"; +import { WidgetNavigation } from "../../src/services/navigation/widget-navigation"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { TransactionWorkflowService } from "../../src/services/workflow/transaction-workflow-service"; +import { + yieldApiActionFixture, + yieldApiTransactionFixture, + yieldApiYieldFixture, +} from "../fixtures"; + +const walletScope = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x1234567890123456789012345678901234567890" + ), + network: "ethereum", +}); + +const makeEnterIntake = (): ClassicTransactionFlowIntake => { + const selectedStake = yieldApiYieldFixture(); + return { + _tag: "Enter", + gasFeeToken: selectedStake.mechanics.gasFeeToken, + providersDetails: [], + request: { + address: walletScope.address, + arguments: { amount: "1" }, + yieldId: selectedStake.id, + } as ActionCommand, + selectedStake, + selectedToken: selectedStake.token, + selectedValidators: new Map(), + walletScope, + }; +}; + +const makeExitIntake = (): ClassicTransactionFlowIntake => { + const integration = yieldApiYieldFixture(); + return { + _tag: "Exit", + gasFeeToken: integration.mechanics.gasFeeToken, + integration, + providersDetails: [], + request: { + address: walletScope.address, + arguments: { amount: "1" }, + yieldId: integration.id, + } as ActionCommand, + unstakeAmount: new BigNumber(1), + unstakeToken: integration.token, + walletScope, + }; +}; + +const makeAppLayer = ( + previewAction: ( + request: ActionPreviewRequest + ) => Effect.Effect, + trackEvent: TrackingService["Service"]["trackEvent"] = () => Effect.void +) => + Layer.mergeAll( + Layer.succeed( + YieldOperations, + YieldOperations.of({ previewAction } as never) + ), + Layer.succeed( + TrackingService, + TrackingService.of({ + trackEvent, + trackPageView: () => Effect.void, + }) + ), + Layer.succeed( + WidgetNavigation, + WidgetNavigation.of({ + back: () => Effect.void, + push: () => Effect.void, + replace: () => Effect.void, + }) + ) + ); + +const makeWorkflowLayer = (probe?: { disposed: number; started: number }) => + Layer.succeed( + TransactionWorkflowService, + TransactionWorkflowService.of({ + make: () => + Effect.acquireRelease( + Effect.sync(() => { + if (probe) probe.started += 1; + return { + dispatch: () => Effect.void, + events: Stream.never, + states: Stream.never, + }; + }), + () => + Effect.sync(() => { + if (probe) probe.disposed += 1; + }) + ), + }) + ); + +const makeRegistry = ( + previewAction: ( + request: ActionPreviewRequest + ) => Effect.Effect, + probe?: { disposed: number; started: number }, + trackEvent?: TrackingService["Service"]["trackEvent"] +) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + makeAppLayer(previewAction, trackEvent) as never + ), + Atom.initialValue(walletRuntime.layer, makeWorkflowLayer(probe) as never), + ], + }); + +describe("Classic Flow Session module", () => { + it("hands Review into Execution and creates a fresh attempt on Back", async () => { + const actions = [ + yieldApiActionFixture({ id: "action-1" }), + yieldApiActionFixture({ id: "action-2" }), + ]; + let previewCalls = 0; + const store = classicFlowSessionStore; + const registry = makeRegistry(() => + Effect.succeed(actions[previewCalls++]!) + ); + registry.set( + store.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const session = registry.get(store.currentSessionAtom); + if (!session) throw new Error("Expected a Flow Session"); + + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const flow = registry.get(rootAtom); + + const firstReviewAtom = makeClassicFlowReviewScope(flow); + const firstReview = registry.get(firstReviewAtom); + const disposeFirstReview = registry.mount(firstReview.reviewViewAtom); + await vi.waitFor(() => + expect(registry.get(firstReview.reviewViewAtom).action?.id).toBe( + "action-1" + ) + ); + + registry.set(firstReview.confirmAtom, undefined); + + const firstExecutionAtom = makeClassicFlowExecutionScope(flow); + const firstExecution = registry.get(firstExecutionAtom); + if (!firstExecution) throw new Error("Expected an Execution module"); + expect(registry.get(firstExecution.actionAtom).id).toBe("action-1"); + + disposeFirstReview(); + registry.set(firstExecution.backAtom, undefined); + registry.set(firstExecution.backAtom, undefined); + + const secondReviewAtom = makeClassicFlowReviewScope(flow); + const secondReview = registry.get(secondReviewAtom); + const disposeSecondReview = registry.mount(secondReview.reviewViewAtom); + await vi.waitFor(() => + expect(registry.get(secondReview.reviewViewAtom).action?.id).toBe( + "action-2" + ) + ); + + registry.set(secondReview.confirmAtom, undefined); + const secondExecution = registry.get(makeClassicFlowExecutionScope(flow)); + if (!secondExecution) throw new Error("Expected a second Execution module"); + expect(registry.get(secondExecution.actionAtom).id).toBe("action-2"); + + disposeSecondReview(); + disposeSession(); + await vi.waitFor(() => + expect(registry.get(store.currentSessionAtom)).toBeNull() + ); + }); + + it("does not let an exiting session clear or navigate a replacement", () => { + const store = classicFlowSessionStore; + const registry = makeRegistry(() => + Effect.succeed(yieldApiActionFixture()) + ); + registry.set( + store.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const first = registry.get(store.currentSessionAtom); + if (!first) throw new Error("Expected the first Flow Session"); + const firstRoot = makeClassicFlowSessionModule(first); + const disposeFirst = registry.mount(firstRoot); + const firstReview = registry.get( + makeClassicFlowReviewScope(registry.get(firstRoot)) + ); + + registry.set( + store.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const second = registry.get(store.currentSessionAtom); + if (!second) throw new Error("Expected the replacement Flow Session"); + const secondRoot = makeClassicFlowSessionModule(second); + const disposeSecond = registry.mount(secondRoot); + + registry.set(firstReview.confirmAtom, undefined); + expect( + registry.get(makeClassicFlowExecutionScope(registry.get(firstRoot))) + ).toBeNull(); + disposeFirst(); + expect(registry.get(store.currentSessionAtom)).toBe(second); + + disposeSecond(); + }); + + it("previews a new Activity action and disposes each scoped workflow", async () => { + const previews = [ + yieldApiActionFixture({ id: "fresh-action-1" }), + yieldApiActionFixture({ id: "fresh-action-2" }), + ]; + let previewCalls = 0; + const previewAction = vi.fn(() => + Effect.succeed(previews[previewCalls++]!) + ); + const probe = { disposed: 0, started: 0 }; + const store = classicFlowSessionStore; + const registry = makeRegistry(previewAction, probe); + const selectedYield = yieldApiYieldFixture(); + registry.set( + store.startAtom, + makeStartClassicFlowSession({ + _tag: "ActivityResume", + action: yieldApiActionFixture({ id: "old-action" }), + providersDetails: [], + selectedValidators: [], + selectedYield, + walletScope, + }) + ); + const session = registry.get(store.currentSessionAtom); + if (!session) throw new Error("Expected an Activity Flow Session"); + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const flow = registry.get(rootAtom); + expect( + registry.get(flow.facade.activityHistoryViewAtom).selectedAction.id + ).toBe("old-action"); + + const firstReview = registry.get(makeClassicFlowReviewScope(flow)); + const disposeFirstReview = registry.mount( + firstReview.activityReviewViewAtom + ); + await vi.waitFor(() => + expect(registry.get(firstReview.activityReviewViewAtom).action?.id).toBe( + "fresh-action-1" + ) + ); + registry.set(firstReview.confirmAtom, undefined); + const firstExecutionAtom = makeClassicFlowExecutionScope(flow); + const disposeFirstExecution = registry.mount(firstExecutionAtom); + const firstExecution = registry.get(firstExecutionAtom); + if (!firstExecution) throw new Error("Expected the first Execution module"); + expect(registry.get(firstExecution.actionAtom).id).toBe("fresh-action-1"); + expect( + registry.get(firstExecution.activityCompleteViewAtom).selectedAction.id + ).toBe("fresh-action-1"); + await vi.waitFor(() => expect(probe.started).toBe(1)); + + registry.set(firstExecution.backAtom, undefined); + disposeFirstExecution(); + disposeFirstReview(); + await vi.waitFor(() => expect(probe.disposed).toBe(1)); + + const secondReview = registry.get(makeClassicFlowReviewScope(flow)); + const disposeSecondReview = registry.mount( + secondReview.activityReviewViewAtom + ); + await vi.waitFor(() => + expect(registry.get(secondReview.activityReviewViewAtom).action?.id).toBe( + "fresh-action-2" + ) + ); + registry.set(secondReview.confirmAtom, undefined); + const secondExecutionAtom = makeClassicFlowExecutionScope(flow); + const disposeSecondExecution = registry.mount(secondExecutionAtom); + const secondExecution = registry.get(secondExecutionAtom); + if (!secondExecution) throw new Error("Expected a second Execution module"); + expect(registry.get(secondExecution.actionAtom).id).toBe("fresh-action-2"); + await vi.waitFor(() => expect(probe.started).toBe(2)); + expect(previewAction).toHaveBeenCalledTimes(2); + + disposeSecondExecution(); + disposeSecondReview(); + await vi.waitFor(() => expect(probe.disposed).toBe(2)); + disposeSession(); + }); + + it("blocks confirmation when the resumed Activity action is seven days old", async () => { + vi.useFakeTimers(); + try { + const now = DateTime.makeUnsafe("2026-07-23T12:00:00.000Z"); + vi.setSystemTime(DateTime.toEpochMillis(now)); + const store = classicFlowSessionStore; + const registry = makeRegistry(() => + Effect.succeed(yieldApiActionFixture({ id: "fresh-action" })) + ); + registry.set( + store.startAtom, + makeStartClassicFlowSession({ + _tag: "ActivityResume", + action: yieldApiActionFixture({ + id: "expired-action", + createdAt: DateTime.subtractDuration(now, Duration.days(7)), + }), + providersDetails: [], + selectedValidators: [], + selectedYield: yieldApiYieldFixture(), + walletScope, + }) + ); + const session = registry.get(store.currentSessionAtom); + if (!session) throw new Error("Expected an Activity Flow Session"); + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const review = registry.get( + makeClassicFlowReviewScope(registry.get(rootAtom)) + ); + const disposeReview = registry.mount(review.activityReviewViewAtom); + + await vi.advanceTimersByTimeAsync(0); + expect(registry.get(review.activityReviewViewAtom).actionExpired).toBe( + true + ); + expect(registry.get(review.activityReviewViewAtom).confirmDisabled).toBe( + true + ); + + registry.set(review.confirmAtom, undefined); + expect( + registry.get(makeClassicFlowExecutionScope(registry.get(rootAtom))) + ).toBeNull(); + + disposeReview(); + disposeSession(); + } finally { + vi.useRealTimers(); + } + }); + + it("disposes the workflow when its Execution scope exits", async () => { + const probe = { disposed: 0, started: 0 }; + const registry = makeRegistry( + () => Effect.succeed(yieldApiActionFixture()), + probe + ); + registry.set( + classicFlowSessionStore.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const session = registry.get(classicFlowSessionStore.currentSessionAtom); + if (!session) throw new Error("Expected a Flow Session"); + + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const flow = registry.get(rootAtom); + const review = registry.get(makeClassicFlowReviewScope(flow)); + const disposeReview = registry.mount(review.reviewViewAtom); + await vi.waitFor(() => + expect(registry.get(review.reviewViewAtom).action).not.toBeNull() + ); + registry.set(review.confirmAtom, undefined); + + const executionAtom = makeClassicFlowExecutionScope(flow); + const disposeExecution = registry.mount(executionAtom); + const execution = registry.get(executionAtom); + if (!execution) throw new Error("Expected an Execution module"); + await vi.waitFor(() => expect(probe.started).toBe(1)); + + const disposeStepsConsumer = registry.mount(execution.workflow.viewAtom); + disposeExecution(); + await vi.waitFor(() => expect(probe.disposed).toBe(1)); + + disposeStepsConsumer(); + expect(probe.disposed).toBe(1); + disposeReview(); + disposeSession(); + }); + + it("promotes and tracks Exit confirmation only once", async () => { + const trackEvent = vi.fn(() => Effect.void); + const registry = makeRegistry( + () => Effect.succeed(yieldApiActionFixture()), + undefined, + trackEvent + ); + registry.set( + classicFlowSessionStore.startAtom, + makeStartClassicFlowSession(makeExitIntake()) + ); + const session = registry.get(classicFlowSessionStore.currentSessionAtom); + if (!session) throw new Error("Expected an Exit Flow Session"); + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const review = registry.get( + makeClassicFlowReviewScope(registry.get(rootAtom)) + ); + const disposeReview = registry.mount(review.reviewViewAtom); + await vi.waitFor(() => + expect(registry.get(review.reviewViewAtom).action).not.toBeNull() + ); + + registry.set(review.confirmAtom, undefined); + registry.set(review.confirmAtom, undefined); + + await vi.waitFor(() => expect(trackEvent).toHaveBeenCalledOnce()); + disposeReview(); + disposeSession(); + }); + + it("suppresses tracking from an exiting Review scope", async () => { + const trackEvent = vi.fn(() => Effect.void); + const registry = makeRegistry( + () => Effect.succeed(yieldApiActionFixture()), + undefined, + trackEvent + ); + registry.set( + classicFlowSessionStore.startAtom, + makeStartClassicFlowSession(makeExitIntake()) + ); + const session = registry.get(classicFlowSessionStore.currentSessionAtom); + if (!session) throw new Error("Expected an Exit Flow Session"); + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const review = registry.get( + makeClassicFlowReviewScope(registry.get(rootAtom)) + ); + const disposeReview = registry.mount(review.reviewViewAtom); + await vi.waitFor(() => + expect(registry.get(review.reviewViewAtom).action).not.toBeNull() + ); + + registry.set( + classicFlowSessionStore.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + registry.set(review.confirmAtom, undefined); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(trackEvent).not.toHaveBeenCalled(); + disposeReview(); + disposeSession(); + }); + + it("does not promote an invalid Exit preview", async () => { + const previewAction = vi.fn(() => + Effect.succeed( + yieldApiActionFixture({ + transactions: [ + yieldApiTransactionFixture({ + id: "failed-transaction", + status: "FAILED", + }), + ], + }) + ) + ); + const store = classicFlowSessionStore; + const registry = makeRegistry(previewAction); + registry.set( + store.startAtom, + makeStartClassicFlowSession(makeExitIntake()) + ); + const session = registry.get(store.currentSessionAtom); + if (!session) throw new Error("Expected an Exit Flow Session"); + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const review = registry.get( + makeClassicFlowReviewScope(registry.get(rootAtom)) + ); + const disposeReview = registry.mount(review.reviewViewAtom); + + await vi.waitFor(() => + expect(registry.get(review.reviewViewAtom).actionPreviewLoading).toBe( + false + ) + ); + registry.set(review.confirmAtom, undefined); + expect(previewAction).toHaveBeenCalledOnce(); + + disposeReview(); + disposeSession(); + }); + + it("retries an ordinary preview failure through Confirm", async () => { + let previewCalls = 0; + const previewAction = vi.fn(() => { + previewCalls += 1; + return previewCalls === 1 + ? Effect.fail(new Error("preview unavailable")) + : Effect.succeed(yieldApiActionFixture({ id: "retried-action" })); + }); + const store = classicFlowSessionStore; + const registry = makeRegistry(previewAction); + registry.set( + store.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const session = registry.get(store.currentSessionAtom); + if (!session) throw new Error("Expected a Flow Session"); + const rootAtom = makeClassicFlowSessionModule(session); + const disposeSession = registry.mount(rootAtom); + const review = registry.get( + makeClassicFlowReviewScope(registry.get(rootAtom)) + ); + const disposeReview = registry.mount(review.reviewViewAtom); + + await vi.waitFor(() => + expect(registry.get(review.reviewViewAtom).actionPreviewLoading).toBe( + false + ) + ); + registry.set(review.confirmAtom, undefined); + registry.set(review.confirmAtom, undefined); + await vi.waitFor(() => + expect(registry.get(review.reviewViewAtom).action?.id).toBe( + "retried-action" + ) + ); + registry.set(review.confirmAtom, undefined); + expect(previewAction).toHaveBeenCalledTimes(2); + + disposeReview(); + disposeSession(); + }); +}); diff --git a/packages/widget/tests/features/classic-flow-session-path.test.ts b/packages/widget/tests/features/classic-flow-session-path.test.ts new file mode 100644 index 000000000..38ed04772 --- /dev/null +++ b/packages/widget/tests/features/classic-flow-session-path.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import { isClassicFlowSessionPath } from "../../src/app/routes/classic-flow-session-path"; + +describe("Classic Flow Session route lifetime", () => { + it.each([ + ["Enter", "/review"], + ["Enter", "/steps"], + ["Enter", "/complete"], + ["Exit", "/positions/yield/balance/unstake/review"], + ["Exit", "/positions/yield/balance/unstake/steps"], + ["Manage", "/positions/yield/balance/pending-action/review"], + ["Manage", "/positions/yield/balance/pending-action/complete"], + ["ActivityResume", "/activity/review"], + ["ActivityResume", "/activity/stake/steps"], + ["ActivityResume", "/activity/unstake/complete"], + ] as const)("keeps %s mounted at %s", (variant, pathname) => { + expect(isClassicFlowSessionPath(pathname, variant)).toBe(true); + }); + + it("ends the route lifetime outside the matching journey", () => { + expect(isClassicFlowSessionPath("/", "Enter")).toBe(false); + expect(isClassicFlowSessionPath("/review", "Exit")).toBe(false); + expect(isClassicFlowSessionPath("/activity", "ActivityResume")).toBe(false); + }); +}); diff --git a/packages/widget/tests/features/classic-flow-session-store.test.ts b/packages/widget/tests/features/classic-flow-session-store.test.ts new file mode 100644 index 000000000..f481a999a --- /dev/null +++ b/packages/widget/tests/features/classic-flow-session-store.test.ts @@ -0,0 +1,145 @@ +import { Schema } from "effect"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { applicationRouterAtom } from "../../src/app/runtime/application-router-runtime"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + classicFlowSessionStore, + finishClassicTransactionFlowAtom, + makeClassicFlowSessionStore, + makeStartClassicFlowSession, +} from "../../src/features/classic-transaction-flow/facade"; +import { + type ClassicTransactionFlowIntake, + isClassicTransactionFlowWalletScopeValid, +} from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture } from "../fixtures"; + +const walletScope = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x1234567890123456789012345678901234567890" + ), + network: "ethereum", +}); + +const makeEnterIntake = (): ClassicTransactionFlowIntake => { + const selectedStake = yieldApiYieldFixture(); + + return { + _tag: "Enter", + gasFeeToken: selectedStake.mechanics.gasFeeToken, + providersDetails: [{ name: "StakeKit" }], + request: { + address: walletScope.address, + arguments: { amount: "1" }, + yieldId: selectedStake.id, + }, + selectedStake, + selectedToken: selectedStake.token, + selectedValidators: new Map(), + walletScope, + }; +}; + +describe("Classic Flow Session intake store", () => { + it("finishes only the current Flow Session through runtime navigation", async () => { + const registry = AtomRegistry.make({ + initialValues: [ + [ + widgetConfigAtom, + normalizeWidgetConfig({ apiKey: "test", variant: "default" }), + ], + ], + }); + + try { + const router = registry.get(applicationRouterAtom); + await router.navigate("/review"); + registry.set( + classicFlowSessionStore.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const first = registry.get(classicFlowSessionStore.currentSessionAtom); + registry.set( + classicFlowSessionStore.startAtom, + makeStartClassicFlowSession(makeEnterIntake()) + ); + const second = registry.get(classicFlowSessionStore.currentSessionAtom); + if (!first || !second) throw new Error("Expected Flow Sessions"); + + registry.set(finishClassicTransactionFlowAtom, first.epoch); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(router.state.location.pathname).toBe("/review"); + + registry.set(finishClassicTransactionFlowAtom, second.epoch); + await expect.poll(() => router.state.location.pathname).toBe("/"); + } finally { + registry.dispose(); + } + }); + + it("isolates equal sessions and ignores cleanup from the replaced session", () => { + const store = makeClassicFlowSessionStore(); + const registry = AtomRegistry.make(); + const intake = makeEnterIntake(); + if (intake._tag !== "Enter") throw new Error("Expected Enter intake"); + + registry.set(store.startAtom, makeStartClassicFlowSession(intake)); + const first = registry.get(store.currentSessionAtom); + registry.set(store.startAtom, makeStartClassicFlowSession(intake)); + const second = registry.get(store.currentSessionAtom); + + expect(first).not.toBeNull(); + expect(second).not.toBeNull(); + expect(first?.epoch).toBe(1); + expect(second?.epoch).toBe(2); + expect(second?.intake).not.toBe(intake); + expect(second?.intake.walletScope).not.toBe(intake.walletScope); + expect( + second?.intake._tag === "Enter" ? second.intake.selectedValidators : null + ).not.toBe(intake.selectedValidators); + expect( + second?.intake._tag === "Enter" ? second.intake.request : null + ).not.toBe(intake.request); + expect( + second?.intake._tag === "Enter" ? second.intake.selectedStake : null + ).not.toBe(intake.selectedStake); + + if (first) registry.set(store.clearAtom, first.epoch); + expect(registry.get(store.currentSessionAtom)).toBe(second); + + if (second) registry.set(store.clearAtom, second.epoch); + expect(registry.get(store.currentSessionAtom)).toBeNull(); + }); + + it("validates wallet ownership without coupling to additional addresses", () => { + const intake = makeEnterIntake(); + const sameEvmOwner = new WalletScopeKey({ + additionalAddresses: { + lidoStakeAccounts: ["lido-account"], + stakeAccounts: ["stake-account"], + }, + address: Schema.decodeSync(WalletAddress)( + walletScope.address.toUpperCase() + ), + network: walletScope.network, + }); + const otherNetwork = new WalletScopeKey({ + address: walletScope.address, + network: "base", + }); + + expect(isClassicTransactionFlowWalletScopeValid(intake, sameEvmOwner)).toBe( + true + ); + expect(isClassicTransactionFlowWalletScopeValid(intake, null)).toBe(false); + expect(isClassicTransactionFlowWalletScopeValid(intake, otherNetwork)).toBe( + false + ); + }); +}); diff --git a/packages/widget/tests/features/earn-facade.test.ts b/packages/widget/tests/features/earn-facade.test.ts new file mode 100644 index 000000000..a8b45bf3a --- /dev/null +++ b/packages/widget/tests/features/earn-facade.test.ts @@ -0,0 +1,251 @@ +import { Effect, Layer } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { + earnTokenSelectionViewAtom, + earnValidatorModalEventAtom, + earnValidatorSelectionViewAtom, + earnYieldSelectionViewAtom, + loadMoreEarnTokensAtom, + loadMoreEarnValidatorsAtom, + removeEarnValidatorAtom, + retryEarnPageAtom, + selectEarnValidatorAtom, + setEarnTokenSearchAtom, + setEarnValidatorSearchAtom, + setEarnYieldSearchAtom, +} from "../../src/features/earn/facade"; +import { + earnMachineIntentAtom, + earnMachineViewAtom, +} from "../../src/features/earn/state/atoms-state/machine/atoms"; +import { makeEarnView } from "../../src/features/earn/state/atoms-state/resolver/view-model"; +import { + type EarnTokenOption, + type EarnValidatorsViewResource, + makeDefaultEarnIntent, +} from "../../src/features/earn/state/atoms-state/types"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; +import type { PullPage } from "../../src/shared/effect/pagination"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; +import { decodeValidator } from "../utils/validators"; + +const makePullAtom =
( + items: ReadonlyArray, + pulled: () => void +): Atom.Writable, never>, void> => + Atom.writable( + () => + AsyncResult.success({ + done: false, + items: [{ hasNextPage: true, items }], + }), + () => pulled() + ); + +const noopRememberValidatorsAtom: EarnValidatorsViewResource["rememberValidatorsAtom"] = + Atom.writable( + () => new Map(), + () => {} + ); + +describe("Earn facade", () => { + it("selects cached validators and ignores unknown selection or removal keys", async () => { + const selectedYield = yieldApiYieldFixture(); + const validator = decodeValidator(yieldApiValidatorFixture()); + const trackEvent = vi.fn(() => Effect.void); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + TrackingService, + TrackingService.of({ + trackEvent, + trackPageView: () => Effect.void, + }) + ) as never + ), + Atom.initialValue( + earnMachineViewAtom, + makeEarnView({ + intent: makeDefaultEarnIntent(), + resources: { + validators: { + enabled: true, + items: [validator], + rememberValidatorsAtom: noopRememberValidatorsAtom, + validatorsPullAtom: () => makePullAtom([], () => undefined), + }, + }, + selection: { yield: selectedYield }, + status: "ready", + }) + ), + ], + }); + const unmountSelect = registry.mount(selectEarnValidatorAtom); + const unmountRemove = registry.mount(removeEarnValidatorAtom); + + try { + expect( + registry.get(earnMachineViewAtom).resources.validators.items + ).toEqual([validator]); + registry.set(selectEarnValidatorAtom, "unknown-validator" as never); + await expect + .poll(() => registry.get(selectEarnValidatorAtom).waiting) + .toBe(false); + expect(trackEvent).not.toHaveBeenCalled(); + + registry.set(selectEarnValidatorAtom, validator.key); + await expect + .poll(() => registry.get(selectEarnValidatorAtom).waiting) + .toBe(false); + expect(AsyncResult.isFailure(registry.get(selectEarnValidatorAtom))).toBe( + false + ); + expect( + registry.get(earnMachineIntentAtom).selectedValidatorKeys + ).toContain(validator.key); + await expect.poll(() => trackEvent.mock.calls.length).toBe(1); + + expect(trackEvent).toHaveBeenCalledWith("validatorSelected", { + validatorAddress: validator.address, + validatorName: validator.name, + }); + + registry.set(removeEarnValidatorAtom, "unknown-validator" as never); + await expect + .poll(() => + AsyncResult.isSuccess(registry.get(removeEarnValidatorAtom)) + ) + .toBe(true); + expect(trackEvent).toHaveBeenCalledTimes(1); + + registry.set(earnValidatorModalEventAtom, { _tag: "Opened" }); + await expect + .poll(() => registry.get(earnValidatorModalEventAtom).waiting) + .toBe(false); + expect(trackEvent).toHaveBeenLastCalledWith("selectValidatorModalOpened"); + + registry.set(earnValidatorModalEventAtom, { _tag: "Closed" }); + await expect + .poll(() => registry.get(earnValidatorModalEventAtom).waiting) + .toBe(false); + expect(trackEvent).toHaveBeenLastCalledWith("selectValidatorModalClosed"); + + registry.set(earnValidatorModalEventAtom, { _tag: "ViewMoreClicked" }); + await expect + .poll(() => registry.get(earnValidatorModalEventAtom).waiting) + .toBe(false); + expect(trackEvent).toHaveBeenLastCalledWith( + "selectValidatorViewMoreClicked" + ); + } finally { + unmountRemove(); + unmountSelect(); + registry.dispose(); + } + }); + + it("projects search and routes pagination and retry commands", () => { + const selectedYield = yieldApiYieldFixture(); + const tokenOption = { + amount: "10", + availableYields: [selectedYield.id], + source: "balance", + token: selectedYield.token, + } satisfies EarnTokenOption; + const validator = decodeValidator(yieldApiValidatorFixture()); + const tokenPull = vi.fn(); + const validatorPull = vi.fn(); + const tokenPullAtom = makePullAtom([tokenOption], tokenPull); + const validatorPullAtom = makePullAtom([validator], validatorPull); + const validatorsPullAtom = vi.fn(() => validatorPullAtom); + const retry = vi.fn(); + const retryTargetAtom = Atom.readable( + () => undefined, + () => retry() + ); + const machine = makeEarnView({ + can: { + selectToken: true, + selectValidator: true, + selectYield: true, + submit: true, + }, + intent: makeDefaultEarnIntent(), + resources: { + tokenOptions: { + items: [tokenOption], + pullAtom: tokenPullAtom, + waiting: false, + }, + validators: { + enabled: true, + items: [validator], + rememberValidatorsAtom: noopRememberValidatorsAtom, + validatorsPullAtom, + }, + yields: { items: [selectedYield], waiting: false }, + }, + retryTargetAtom, + selection: { + token: tokenOption, + yield: selectedYield, + }, + status: "ready", + }); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue(earnMachineViewAtom, machine), + Atom.initialValue( + widgetConfigAtom, + normalizeWidgetConfig({ apiKey: "test", variant: "default" }) + ), + ], + }); + + try { + registry.set(retryEarnPageAtom, undefined); + expect(retry).toHaveBeenCalledOnce(); + + const unmountValidators = registry.mount(earnValidatorSelectionViewAtom); + try { + registry.set(setEarnTokenSearchAtom, selectedYield.token.symbol); + registry.set(setEarnYieldSearchAtom, selectedYield.metadata.name); + + expect(registry.get(earnTokenSelectionViewAtom).filtered).toEqual([ + tokenOption, + ]); + expect(registry.get(earnYieldSelectionViewAtom).filtered).toEqual([ + selectedYield, + ]); + + registry.set(loadMoreEarnTokensAtom, undefined); + expect(tokenPull).toHaveBeenCalledOnce(); + + registry.set(loadMoreEarnValidatorsAtom, undefined); + expect(validatorPull).toHaveBeenCalledOnce(); + + registry.set(setEarnValidatorSearchAtom, " validator "); + expect(registry.get(earnValidatorSelectionViewAtom)).toMatchObject({ + isDebouncing: true, + isLoading: true, + search: " validator ", + }); + } finally { + unmountValidators(); + } + } finally { + registry.dispose(); + } + }); +}); diff --git a/packages/widget/tests/features/earn-force-max.test.ts b/packages/widget/tests/features/earn-force-max.test.ts new file mode 100644 index 000000000..e9a656e27 --- /dev/null +++ b/packages/widget/tests/features/earn-force-max.test.ts @@ -0,0 +1,300 @@ +import { Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import type { EarnYield } from "../../src/domain/schema/earn-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import type { PositionsData } from "../../src/domain/types/positions"; +import { getEnterAmountConstraint } from "../../src/domain/types/stake"; +import { + earnYieldCatalogAtom, + initYieldAtom, + mergedTokenOptionsAtom, + positionsDataAtom, +} from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { + InitYieldKey, + PositionsDataKey, + TokenOptionsKey, + YieldCatalogKey, +} from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { + canSubmitEarnForm, + resolveForm, +} from "../../src/features/earn/state/atoms-state/resolver/form"; +import { resolveEarnView } from "../../src/features/earn/state/atoms-state/resolver/view"; +import { + type EarnEntry, + type EarnMachineIntent, + makeDefaultEarnIntent, +} from "../../src/features/earn/state/atoms-state/types"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldDtoFixture, yieldApiYieldFixture } from "../fixtures"; + +const positionsData: PositionsData = new Map(); +const address = Schema.decodeSync(WalletAddress)("0xwallet"); + +const withAmountRange = (minimum: number, maximum: number): EarnYield => { + const yieldDto = yieldApiYieldDtoFixture(); + + return yieldApiYieldFixture({ + mechanics: { + ...yieldDto.mechanics, + arguments: { + ...yieldDto.mechanics.arguments, + enter: { + fields: [ + { + label: "Amount", + maximum: maximum.toString(), + minimum: minimum.toString(), + name: "amount", + required: true, + type: "string", + }, + ], + }, + }, + }, + }); +}; + +const resolve = ({ + availableAmount, + intent = makeDefaultEarnIntent(), + selectedYield, +}: { + availableAmount: string | null; + intent?: EarnMachineIntent; + selectedYield: EarnYield; +}) => + resolveForm({ + availableAmount, + intent, + positionsData, + selectedYield, + }); + +describe("Earn force-max amount resolution", () => { + const forceMaxYield = withAmountRange(-1, -1); + + it("recognizes the sentinel before numeric range calculation", () => { + expect(getEnterAmountConstraint(forceMaxYield, positionsData)).toEqual({ + type: "force-max", + }); + }); + + it("uses the available balance and never exposes the sentinel", () => { + const form = resolve({ + availableAmount: "10", + selectedYield: forceMaxYield, + }); + + expect(form.stakeAmount).toBe("10"); + expect(form.stakeAmount).not.toBe("-1"); + expect(form.useMaxAmount).toBe(true); + }); + + it("resolves view.form from the balance and disables submit until it is available", () => { + const scope = new WalletScopeKey({ + additionalAddresses: null, + address, + network: "ethereum", + }); + const entry: EarnEntry = { + categoryOrder: [], + dashboardVariant: false, + initParams: null, + walletScope: scope, + walletResolution: "settled", + }; + const makeView = (source: "balance" | "default") => { + const tokenOptionsKey = new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope, + tokensForEnabledYieldsOnly: false, + }); + const positionsKey = new PositionsDataKey({ + scope, + }); + const yieldCatalogKey = new YieldCatalogKey({ + category: null, + network: forceMaxYield.token.network, + yieldIds: [forceMaxYield.id], + }); + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + mergedTokenOptionsAtom(tokenOptionsKey), + AsyncResult.success([ + { + amount: source === "balance" ? "10" : "0", + availableYields: [forceMaxYield.id], + source, + token: forceMaxYield.token, + }, + ]), + ], + [positionsDataAtom(positionsKey), AsyncResult.success(positionsData)], + [ + earnYieldCatalogAtom(yieldCatalogKey), + AsyncResult.success([forceMaxYield]), + ], + ], + }); + const viewAtom = Atom.make((context) => + resolveEarnView({ + context, + entry, + intent: makeDefaultEarnIntent(), + }) + ); + + try { + return registry.get(viewAtom); + } finally { + registry.dispose(); + } + }; + + const withBalance = makeView("balance"); + const withoutBalance = makeView("default"); + + expect(withBalance.form.stakeAmount).toBe("10"); + expect(withBalance.can.submit).toBe(true); + expect(withoutBalance.form.stakeAmount).toBe("0"); + expect(withoutBalance.can.submit).toBe(false); + }); + + it("overrides stale amount intent and follows balance revalidation", () => { + const intent = { + ...makeDefaultEarnIntent(), + stakeAmount: "4", + }; + + expect( + resolve({ availableAmount: "10", intent, selectedYield: forceMaxYield }) + .stakeAmount + ).toBe("10"); + expect( + resolve({ availableAmount: "12", intent, selectedYield: forceMaxYield }) + .stakeAmount + ).toBe("12"); + }); + + it("uses a safe zero while the force-max balance is unresolved", () => { + expect( + resolve({ availableAmount: null, selectedYield: forceMaxYield }) + .stakeAmount + ).toBe("0"); + }); + + it("preserves ordinary range defaults and explicit intent", () => { + const rangeYield = withAmountRange(2, 20); + + expect( + resolve({ availableAmount: "10", selectedYield: rangeYield }).stakeAmount + ).toBe("2"); + expect( + resolve({ + availableAmount: "10", + intent: { + ...makeDefaultEarnIntent(), + amountInput: "manual", + stakeAmount: "7", + }, + selectedYield: rangeYield, + }).stakeAmount + ).toBe("7"); + }); + + it("preserves an explicit zero so validation can reject it", () => { + const rangeYield = withAmountRange(2, 20); + + expect( + resolve({ + availableAmount: "10", + intent: { + ...makeDefaultEarnIntent(), + amountInput: "manual", + stakeAmount: "0", + }, + selectedYield: rangeYield, + }).stakeAmount + ).toBe("0"); + }); + + it("rejects zero, below-minimum, above-maximum, and above-balance amounts", () => { + const rangeYield = withAmountRange(2, 20); + const canSubmit = (stakeAmount: string) => + canSubmitEarnForm({ + availableAmount: "10", + form: { + providerYieldId: null, + stakeAmount, + tronResource: null, + useMaxAmount: false, + }, + positionsData, + selectedYield: rangeYield, + }); + + expect(canSubmit("0")).toBe(false); + expect(canSubmit("1")).toBe(false); + expect(canSubmit("21")).toBe(false); + expect(canSubmit("11")).toBe(false); + expect(canSubmit("7")).toBe(true); + }); + + it("uses only advertised provider and Tron options", () => { + const base = yieldApiYieldDtoFixture(); + const selectedYield = yieldApiYieldFixture({ + mechanics: { + ...base.mechanics, + arguments: { + ...base.mechanics.arguments, + enter: { + fields: [ + { + label: "Provider", + name: "providerId", + options: ["ethereum-provider-a"], + required: true, + type: "string", + }, + { + label: "Resource", + name: "tronResource", + options: ["BANDWIDTH"], + required: true, + type: "enum", + }, + ], + }, + }, + }, + }); + const form = resolve({ + availableAmount: "10", + intent: { + ...makeDefaultEarnIntent(), + selectedProviderYieldId: Schema.decodeSync(YieldId)( + "ethereum-provider-b" + ), + tronResource: "ENERGY", + }, + selectedYield, + }); + + expect(form.providerYieldId).toBe("ethereum-provider-a"); + expect(form.tronResource).toBe("BANDWIDTH"); + }); +}); diff --git a/packages/widget/tests/features/earn-page-workflow.test.ts b/packages/widget/tests/features/earn-page-workflow.test.ts new file mode 100644 index 000000000..9980cc042 --- /dev/null +++ b/packages/widget/tests/features/earn-page-workflow.test.ts @@ -0,0 +1,137 @@ +import { Option, Schema } from "effect"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { widgetConfigAtom } from "../../src/app/config/settings"; +import { YieldId } from "../../src/domain/schema/identifiers"; +import { + earnMachineEntryAtom, + earnMachineIntentAtom, + earnMachineViewAtom, + resolveWalletMachineView, +} from "../../src/features/earn/state/atoms-state/machine/atoms"; +import { + earnPageInputAtom, + earnPageQuoteAtom, + earnPageSearchAtom, + earnPageSelectionAtom, + earnPageSubmittedAtom, +} from "../../src/features/earn/state/page-workflow"; + +describe("earn page workflow atoms", () => { + it("derives input, selection, and quote models from the feature machine", () => { + const registry = AtomRegistry.make(); + + expect(registry.get(earnPageInputAtom).stakeAmount).toBe("0"); + expect(registry.get(earnPageSelectionAtom).yield).toBeNull(); + expect(registry.get(earnPageQuoteAtom).stakeAmount.toFixed()).toBe("0"); + }); + + it("preserves machine intent when runtime inputs change", () => { + const registry = AtomRegistry.make(); + + expect(registry.get(earnMachineEntryAtom).tokensForEnabledYieldsOnly).toBe( + false + ); + registry.set(earnMachineIntentAtom, { + type: "category/select", + category: "defi", + }); + registry.set(widgetConfigAtom, { + ...registry.get(widgetConfigAtom), + tokensForEnabledYieldsOnly: true, + }); + + expect(registry.get(earnMachineEntryAtom).tokensForEnabledYieldsOnly).toBe( + true + ); + expect(registry.get(earnMachineIntentAtom).selectedCategory).toBe("defi"); + }); + + it("publishes resolving-wallet while retaining the selection snapshot", () => { + const registry = AtomRegistry.make(); + const previousView = registry.get(earnMachineViewAtom); + const nextView = resolveWalletMachineView({ + entry: { + ...registry.get(earnMachineEntryAtom), + walletResolution: "pending", + }, + previous: Option.some(previousView), + resolved: previousView, + }); + + expect(nextView).toMatchObject({ + can: { + selectToken: false, + selectYield: false, + selectValidator: false, + submit: false, + }, + selection: previousView.selection, + status: "resolving-wallet", + }); + registry.dispose(); + }); + + it("isolates machine intent between widget registries", () => { + const firstRegistry = AtomRegistry.make(); + const secondRegistry = AtomRegistry.make(); + + firstRegistry.set(earnMachineIntentAtom, { + type: "category/select", + category: "rwa", + }); + + expect(firstRegistry.get(earnMachineIntentAtom).selectedCategory).toBe( + "rwa" + ); + expect( + secondRegistry.get(earnMachineIntentAtom).selectedCategory + ).toBeNull(); + }); + + it("owns searches and submission state", () => { + const registry = AtomRegistry.make(); + + registry.set(earnPageSearchAtom, { + stake: "ethereum", + token: "eth", + validator: "validator", + }); + registry.set(earnPageSubmittedAtom, true); + + expect(registry.get(earnPageSearchAtom).token).toBe("eth"); + expect(registry.get(earnPageSubmittedAtom)).toBe(true); + }); + + it("resets submission state when category, yield, or token changes", () => { + const registry = AtomRegistry.make(); + + registry.set(earnPageSubmittedAtom, true); + registry.set(earnMachineIntentAtom, { + type: "category/select", + category: "defi", + }); + expect(registry.get(earnPageSubmittedAtom)).toBe(false); + + registry.set(earnPageSubmittedAtom, true); + registry.set(earnMachineIntentAtom, { + type: "yield/select", + yieldId: Schema.decodeSync(YieldId)("yield-1"), + }); + expect(registry.get(earnPageSubmittedAtom)).toBe(false); + + registry.set(earnPageSubmittedAtom, true); + registry.set(earnMachineIntentAtom, { + type: "token/select", + tokenKey: "ethereum-0xtoken", + }); + expect(registry.get(earnPageSubmittedAtom)).toBe(false); + + registry.set(earnPageSubmittedAtom, true); + registry.set(earnMachineIntentAtom, { + type: "stakeAmount/change", + amount: "1", + }); + expect(registry.get(earnPageSubmittedAtom)).toBe(true); + }); +}); diff --git a/packages/widget/tests/features/earn-state-machine-catalog.test.ts b/packages/widget/tests/features/earn-state-machine-catalog.test.ts new file mode 100644 index 000000000..04b20590a --- /dev/null +++ b/packages/widget/tests/features/earn-state-machine-catalog.test.ts @@ -0,0 +1,210 @@ +import { Effect, Layer, Option } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { widgetConfigAtom } from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { + availableYieldCategoriesAtom, + earnYieldCatalogAtom, + yieldValidatorsAtom, +} from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { + AvailableYieldCategoriesKey, + YieldCatalogKey, + YieldValidatorsKey, +} from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { + MultiYieldsKey, + visibleMultiYieldsAtom, +} from "../../src/features/yield-summary/multi-yields"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { + yieldApiProviderFixture, + yieldApiValidatorFixture, + yieldApiYieldFixture, +} from "../fixtures"; + +describe("Earn state machine catalog", () => { + it("refreshes the responsible authoritative source through the catalog projection", () => { + const yieldModel = yieldApiYieldFixture(); + let offline = true; + const listYields = vi.fn(() => + offline + ? Effect.fail( + new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-directory", + }) + ) + : Effect.succeed({ + items: [yieldModel], + limit: 100, + offset: 0, + total: 1, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ listYields } as never) + ) + ), + ], + }); + const resource = earnYieldCatalogAtom( + new YieldCatalogKey({ + category: null, + network: yieldModel.token.network, + yieldIds: [yieldModel.id], + }) + ); + + expect(AsyncResult.isFailure(registry.get(resource))).toBe(true); + const attemptsBeforeRetry = listYields.mock.calls.length; + offline = false; + registry.refresh(resource); + + expect(AsyncResult.getOrThrow(registry.get(resource))).toEqual([ + yieldModel, + ]); + expect(listYields).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); + + it("keeps API-scoped yields visible to provider selection", () => { + const yieldModel = yieldApiYieldFixture({ id: "avax-native-staking" }); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, { + getProvider: () => + Effect.succeed(Option.some(yieldApiProviderFixture())), + listYields: () => + Effect.succeed({ + items: [yieldModel], + limit: 100, + offset: 0, + total: 1, + }), + } as never) + ), + ], + }); + const result = registry.get( + visibleMultiYieldsAtom(new MultiYieldsKey({ yieldIds: [yieldModel.id] })) + ); + + expect(AsyncResult.getOrThrow(result)?.map(({ id }) => id)).toEqual([ + yieldModel.id, + ]); + }); + + it("keeps a category available when its enter-enabled yield has zero reward", () => { + const yieldModel = yieldApiYieldFixture({ + rewardRate: { components: [], rateType: "APY", total: 0 }, + }); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, { + listYields: () => + Effect.succeed({ + items: [yieldModel], + limit: 100, + offset: 0, + total: 1, + }), + } as never) + ), + ], + }); + const result = registry.get( + availableYieldCategoriesAtom( + new AvailableYieldCategoriesKey({ + categoryOrder: ["stake"], + network: "ethereum", + }) + ) + ); + + expect(AsyncResult.getOrThrow(result)).toEqual(["stake"]); + }); + + it("exposes required validator initial acquisition state", () => { + const yieldModel = yieldApiYieldFixture(); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, { + listValidators: () => Effect.never, + } as never) + ), + ], + }); + const validators = yieldValidatorsAtom( + new YieldValidatorsKey({ selectedYieldId: yieldModel.id }) + ); + const result = registry.get(validators.initialValidatorsResultAtom); + + expect(AsyncResult.isWaiting(result)).toBe(true); + expect(AsyncResult.value(result)).toEqual(Option.none()); + }); + + it("applies validator configuration before selection and readiness", () => { + const yieldModel = yieldApiYieldFixture(); + const allowed = { + ...yieldApiValidatorFixture({ address: "0xallowed" }), + key: "0xallowed" as never, + }; + const blocked = { + ...yieldApiValidatorFixture({ address: "0xblocked" }), + key: "0xblocked" as never, + }; + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ + listValidators: () => + Effect.succeed({ + items: [blocked, allowed], + limit: 100, + offset: 0, + total: 2, + }), + } as never) + ) + ), + ], + }); + registry.set(widgetConfigAtom, { + ...registry.get(widgetConfigAtom), + validatorsConfig: { + ethereum: { blocked: [blocked.address] }, + }, + }); + const validators = yieldValidatorsAtom( + new YieldValidatorsKey({ + network: yieldModel.token.network, + selectedYieldId: yieldModel.id, + }) + ); + + const initial = AsyncResult.getOrThrow( + registry.get(validators.initialValidatorsResultAtom) + ); + + expect(initial.map((validator) => validator.address)).toEqual([ + allowed.address, + ]); + expect(registry.get(validators.rememberValidatorsAtom).size).toBe(0); + }); +}); diff --git a/packages/widget/tests/features/earn-state-machine-model.test.ts b/packages/widget/tests/features/earn-state-machine-model.test.ts new file mode 100644 index 000000000..07f8d1bf1 --- /dev/null +++ b/packages/widget/tests/features/earn-state-machine-model.test.ts @@ -0,0 +1,743 @@ +import { Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { EarnValidator } from "../../src/domain/schema/earn-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import { EvmNetworks } from "../../src/domain/types/chains/networks"; +import { tokenString } from "../../src/domain/types/tokens"; +import { + reconcileEarnMachineOwner, + reconcileEarnMachineView, + shouldConsumeEarnInitialization, +} from "../../src/features/earn/state/atoms-state/machine/owner"; +import { applyEarnAction } from "../../src/features/earn/state/atoms-state/machine/reducer"; +import { resolveCategory } from "../../src/features/earn/state/atoms-state/resolver/category"; +import { resolveToken } from "../../src/features/earn/state/atoms-state/resolver/token"; +import { resolveValidators } from "../../src/features/earn/state/atoms-state/resolver/validators"; +import { + resolveYield, + resolveYieldOptions, +} from "../../src/features/earn/state/atoms-state/resolver/yield"; +import { + type EarnMachineIntent, + type EarnMachineView, + makeDefaultEarnIntent, +} from "../../src/features/earn/state/atoms-state/types"; +import { + WalletScopeKey, + WalletScopeOwnerKey, +} from "../../src/services/wallet/domain/scope"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; + +const yieldId = Schema.decodeSync(YieldId)("ethereum-eth-staking"); +const walletAddress = (value: string) => + Schema.decodeSync(WalletAddress)(value); + +describe("Earn state machine model", () => { + it("resets all intent when the primary address owner changes", () => { + const firstOwner = new WalletScopeOwnerKey({ + address: walletAddress("0x1111111111111111111111111111111111111111"), + network: EvmNetworks.Ethereum, + }); + const nextOwner = new WalletScopeOwnerKey({ + address: walletAddress("0x2222222222222222222222222222222222222222"), + network: EvmNetworks.Ethereum, + }); + const intent = applyEarnAction({ + action: { category: "defi", type: "category/select" }, + intent: makeDefaultEarnIntent(), + }); + + expect( + reconcileEarnMachineOwner( + { + dashboardVariant: false, + initializationConsumed: false, + intent, + owner: firstOwner, + }, + nextOwner + ).intent + ).toEqual(makeDefaultEarnIntent()); + }); + + it("does not re-arm one-time initialization when the wallet owner changes", () => { + const firstOwner = new WalletScopeOwnerKey({ + address: walletAddress("0x1111111111111111111111111111111111111111"), + network: EvmNetworks.Ethereum, + }); + const nextOwner = new WalletScopeOwnerKey({ + address: walletAddress("0x2222222222222222222222222222222222222222"), + network: EvmNetworks.Polygon, + }); + + expect( + reconcileEarnMachineOwner( + { + initializationConsumed: true, + dashboardVariant: false, + intent: makeDefaultEarnIntent(), + owner: firstOwner, + }, + nextOwner + ).initializationConsumed + ).toBe(true); + }); + + it("waits for an account-targeted wallet scope before consuming initialization", () => { + const readyView = { status: "ready" } as EarnMachineView; + const initParams = { + accountId: "0x1111111111111111111111111111111111111111", + balanceId: null, + network: null, + pendingaction: null, + tab: null, + token: null, + validator: null, + yieldId, + }; + const baseEntry = { + categoryOrder: [], + dashboardVariant: false, + initParams, + walletResolution: "settled" as const, + walletScope: null, + }; + + expect( + shouldConsumeEarnInitialization({ entry: baseEntry, view: readyView }) + ).toBe(false); + expect( + shouldConsumeEarnInitialization({ + entry: { + ...baseEntry, + walletScope: new WalletScopeKey({ + address: walletAddress(initParams.accountId), + network: EvmNetworks.Ethereum, + }), + }, + view: readyView, + }) + ).toBe(true); + expect( + shouldConsumeEarnInitialization({ + entry: { + ...baseEntry, + initParams: { ...initParams, accountId: null }, + }, + view: readyView, + }) + ).toBe(true); + }); + + it("commits a resolved fallback selection into canonical intent", () => { + const selectedYield = yieldApiYieldFixture(); + const token = { + amount: "0", + availableYields: [selectedYield.id], + source: "default" as const, + token: selectedYield.token, + }; + const view = { + form: { + providerYieldId: null, + stakeAmount: "2", + tronResource: null, + useMaxAmount: false, + }, + selection: { + category: "stake", + token, + validators: [], + yield: selectedYield, + }, + status: "ready", + } as unknown as EarnMachineView; + + expect( + reconcileEarnMachineView(makeDefaultEarnIntent(), view) + ).toMatchObject({ + selectedCategory: "stake", + selectedTokenKey: tokenString(token.token), + selectedYieldId: selectedYield.id, + stakeAmount: "2", + }); + }); + + it("preserves intent when the primary address and network are unchanged", () => { + const firstOwner = new WalletScopeOwnerKey({ + address: walletAddress("0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD"), + network: EvmNetworks.Ethereum, + }); + const sameOwner = new WalletScopeOwnerKey({ + address: walletAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + network: EvmNetworks.Ethereum, + }); + const state = { + dashboardVariant: false, + initializationConsumed: false, + intent: applyEarnAction({ + action: { category: "defi", type: "category/select" }, + intent: makeDefaultEarnIntent(), + }), + owner: firstOwner, + }; + + expect(reconcileEarnMachineOwner(state, sameOwner)).toBe(state); + }); + + it("resets selection when switching between classic and dashboard modes", () => { + const state = { + dashboardVariant: false, + initializationConsumed: true, + intent: applyEarnAction({ + action: { category: "defi", type: "category/select" }, + intent: makeDefaultEarnIntent(), + }), + owner: null, + }; + + expect(reconcileEarnMachineOwner(state, null, true)).toEqual({ + dashboardVariant: true, + initializationConsumed: true, + intent: makeDefaultEarnIntent(), + owner: null, + }); + }); + + it("preserves downstream intent when the active token is selected again", () => { + const intent = { + ...makeDefaultEarnIntent(), + selectedProviderYieldId: yieldId, + selectedTokenKey: "ethereum-eth", + selectedYieldId: yieldId, + stakeAmount: "4", + tronResource: "ENERGY", + useMaxAmount: true, + } satisfies EarnMachineIntent; + + expect( + applyEarnAction({ + action: { type: "token/select", tokenKey: "ethereum-eth" }, + intent, + }) + ).toBe(intent); + }); + + it("resets token and downstream intent when category changes", () => { + const validatorKey = "validator-a" as never; + const intent = { + ...makeDefaultEarnIntent(), + selectedCategory: "stake" as const, + selectedProviderYieldId: yieldId, + selectedTokenKey: "ethereum-eth", + selectedValidatorKeys: new Set([validatorKey]), + selectedYieldId: yieldId, + stakeAmount: "4", + tronResource: "ENERGY", + useMaxAmount: true, + } satisfies EarnMachineIntent; + + expect( + applyEarnAction({ + action: { type: "category/select", category: "defi" }, + intent, + }) + ).toEqual({ + ...makeDefaultEarnIntent(), + selectedCategory: "defi", + }); + }); + + it("does not remove the final selected validator", () => { + const validatorKey = "validator-a" as never; + const intent = { + ...makeDefaultEarnIntent(), + selectedValidatorKeys: new Set([validatorKey]), + }; + + expect( + applyEarnAction({ + action: { type: "validator/remove", validatorKey }, + intent, + }) + ).toBe(intent); + }); + + it("records an explicit zero as manual amount intent", () => { + expect( + applyEarnAction({ + action: { type: "stakeAmount/change", amount: "0" }, + intent: makeDefaultEarnIntent(), + }) + ).toMatchObject({ + amountInput: "manual", + stakeAmount: "0", + useMaxAmount: false, + }); + }); + + it("does not invent a dashboard category after an empty discovery result", () => { + expect( + resolveCategory({ + availableCategories: [], + categoryOrder: ["stake", "defi", "rwa"], + dashboardVariant: true, + selectedCategory: null, + }) + ).toBeNull(); + expect( + resolveCategory({ + availableCategories: ["stake", "defi"], + categoryOrder: ["stake", "defi", "rwa"], + dashboardVariant: true, + selectedCategory: "defi", + }) + ).toBe("defi"); + expect( + resolveCategory({ + availableCategories: ["stake"], + categoryOrder: ["stake", "defi", "rwa"], + dashboardVariant: false, + selectedCategory: "stake", + }) + ).toBeNull(); + }); + + it("does not apply token or yield preferences from another network", () => { + const firstYield = yieldApiYieldFixture({ + id: "ethereum-eth-first", + }); + const secondYield = yieldApiYieldFixture({ + id: "ethereum-eth-second", + }); + const firstToken = { + amount: "0", + availableYields: [firstYield.id, secondYield.id], + source: "default" as const, + token: firstYield.token, + }; + const secondToken = { + ...firstToken, + token: { + ...firstYield.token, + address: "0x2222222222222222222222222222222222222222" as never, + name: "Wrapped ETH", + symbol: "WETH", + }, + }; + const entry = { + categoryOrder: [], + dashboardVariant: false, + initParams: null, + preferredTokenYieldsPerNetwork: { + [EvmNetworks.Polygon]: { + [tokenString(secondToken.token)]: secondYield.id, + }, + }, + walletResolution: "settled" as const, + walletScope: new WalletScopeKey({ + address: walletAddress("0x1111111111111111111111111111111111111111"), + network: EvmNetworks.Ethereum, + }), + }; + + expect( + resolveToken({ + entry, + selectedTokenKey: null, + tokenOptions: [firstToken, secondToken], + }) + ).toBe(firstToken); + expect( + resolveYield({ + entry: { + ...entry, + preferredTokenYieldsPerNetwork: { + [EvmNetworks.Polygon]: { + [tokenString(firstToken.token)]: secondYield.id, + }, + }, + }, + positionsData: new Map(), + selectedToken: firstToken, + selectedYieldId: null, + yieldOptions: [firstYield, secondYield], + }) + ).toBe(firstYield); + expect( + resolveToken({ + entry: { ...entry, walletScope: null }, + selectedTokenKey: null, + tokenOptions: [firstToken, secondToken], + }) + ).toBe(secondToken); + }); + + it("covers every intent command and reset boundary", () => { + const firstValidator = "validator-a" as never; + const secondValidator = "validator-b" as never; + const otherYield = Schema.decodeSync(YieldId)("ethereum-eth-other"); + const populated = { + ...makeDefaultEarnIntent(), + amountInput: "manual" as const, + selectedCategory: "stake" as const, + selectedProviderYieldId: yieldId, + selectedTokenKey: "ethereum-eth", + selectedValidatorKeys: new Set([firstValidator, secondValidator]), + selectedYieldId: yieldId, + stakeAmount: "5", + tronResource: "ENERGY", + useMaxAmount: true, + } satisfies EarnMachineIntent; + + expect( + applyEarnAction({ + action: { tokenKey: "ethereum-usdc", type: "token/select" }, + intent: populated, + }) + ).toEqual({ + ...makeDefaultEarnIntent(), + selectedCategory: "stake", + selectedTokenKey: "ethereum-usdc", + }); + expect( + applyEarnAction({ + action: { type: "yield/select", yieldId }, + intent: populated, + }) + ).toBe(populated); + expect( + applyEarnAction({ + action: { type: "yield/select", yieldId: otherYield }, + intent: populated, + }) + ).toEqual({ + ...populated, + amountInput: "untouched", + selectedProviderYieldId: null, + selectedValidatorKeys: new Set(), + selectedYieldId: otherYield, + stakeAmount: "0", + tronResource: null, + useMaxAmount: false, + }); + expect( + applyEarnAction({ + action: { category: "stake", type: "category/select" }, + intent: populated, + }) + ).toBe(populated); + expect( + applyEarnAction({ + action: { type: "validator/select", validatorKey: firstValidator }, + intent: makeDefaultEarnIntent(), + }).selectedValidatorKeys + ).toEqual(new Set([firstValidator])); + const oneValidator = { + ...makeDefaultEarnIntent(), + selectedValidatorKeys: new Set([firstValidator]), + }; + expect( + applyEarnAction({ + action: { + type: "validator/multiselect", + validatorKey: secondValidator, + }, + intent: oneValidator, + }).selectedValidatorKeys + ).toEqual(new Set([firstValidator, secondValidator])); + expect( + applyEarnAction({ + action: { + type: "validator/multiselect", + validatorKey: firstValidator, + }, + intent: oneValidator, + }) + ).toBe(oneValidator); + expect( + applyEarnAction({ + action: { type: "validator/remove", validatorKey: firstValidator }, + intent: populated, + }).selectedValidatorKeys + ).toEqual(new Set([secondValidator])); + expect( + applyEarnAction({ + action: { + providerYieldId: otherYield, + type: "providerYieldId/select", + }, + intent: populated, + }).selectedProviderYieldId + ).toBe(otherYield); + expect( + applyEarnAction({ + action: { amount: "9", type: "stakeAmount/max" }, + intent: populated, + }) + ).toMatchObject({ + amountInput: "max", + stakeAmount: "9", + useMaxAmount: true, + }); + expect( + applyEarnAction({ + action: { tronResource: "BANDWIDTH", type: "tronResource/select" }, + intent: populated, + }).tronResource + ).toBe("BANDWIDTH"); + }); + + it("applies token precedence across explicit, init-yield, init-token, preferred, and fallback", () => { + const firstYield = yieldApiYieldFixture({ id: "ethereum-eth-first" }); + const secondYield = yieldApiYieldFixture({ id: "ethereum-weth-second" }); + const first = { + amount: "1", + availableYields: [firstYield.id], + source: "balance" as const, + token: { + ...firstYield.token, + address: "0x1111111111111111111111111111111111111111" as never, + }, + }; + const second = { + amount: "0", + availableYields: [secondYield.id], + source: "default" as const, + token: { + ...secondYield.token, + address: "0x2222222222222222222222222222222222222222" as never, + symbol: "WETH", + }, + }; + const baseEntry = { + categoryOrder: [], + dashboardVariant: false, + initParams: null, + walletResolution: "settled" as const, + walletScope: new WalletScopeKey({ + address: walletAddress("0x9999999999999999999999999999999999999999"), + network: EvmNetworks.Ethereum, + }), + }; + const initParams = { + accountId: null, + balanceId: null, + network: EvmNetworks.Ethereum, + pendingaction: null, + tab: null, + token: null, + validator: null, + yieldId: null, + }; + + expect( + resolveToken({ + entry: baseEntry, + selectedTokenKey: tokenString(second.token), + tokenOptions: [first, second], + }) + ).toBe(second); + expect( + resolveToken({ + entry: { + ...baseEntry, + initParams: { ...initParams, yieldId: secondYield.id }, + }, + selectedTokenKey: "missing", + tokenOptions: [first, second], + }) + ).toBe(second); + expect( + resolveToken({ + entry: { + ...baseEntry, + initParams: { ...initParams, token: "weth" }, + }, + selectedTokenKey: null, + tokenOptions: [first, second], + }) + ).toBe(second); + expect( + resolveToken({ + entry: { + ...baseEntry, + preferredTokenYieldsPerNetwork: { + [EvmNetworks.Ethereum]: { + [tokenString(second.token)]: secondYield.id, + }, + }, + }, + selectedTokenKey: null, + tokenOptions: [first, second], + }) + ).toBe(second); + expect( + resolveToken({ + entry: baseEntry, + selectedTokenKey: null, + tokenOptions: [first, second], + }) + ).toBe(first); + expect( + resolveToken({ + entry: baseEntry, + selectedTokenKey: null, + tokenOptions: [], + }) + ).toBeNull(); + }); + + it("applies yield visibility and explicit, init, preferred, and star precedence", () => { + const first = yieldApiYieldFixture({ + id: "ethereum-eth-first", + rewardRate: { components: [], rateType: "APY", total: 0 }, + }); + const second = yieldApiYieldFixture({ id: "ethereum-eth-second" }); + const disabled = yieldApiYieldFixture({ + id: "ethereum-eth-disabled", + status: { enter: false, exit: true }, + }); + const selectedToken = { + amount: "100", + availableYields: [first.id, second.id, disabled.id], + source: "balance" as const, + token: first.token, + }; + const baseEntry = { + categoryOrder: [], + dashboardVariant: false, + initParams: null, + walletResolution: "settled" as const, + walletScope: null, + }; + const options = resolveYieldOptions({ + selectedToken, + yieldsById: [first, second, disabled], + }); + + expect( + resolveYieldOptions({ selectedToken: null, yieldsById: options }) + ).toEqual([]); + expect(options).toEqual([first, second]); + expect( + resolveYield({ + entry: baseEntry, + positionsData: new Map(), + selectedToken, + selectedYieldId: second.id, + yieldOptions: options, + }) + ).toBe(second); + expect( + resolveYield({ + entry: { + ...baseEntry, + initParams: { + accountId: null, + balanceId: null, + network: null, + pendingaction: null, + tab: null, + token: null, + validator: null, + yieldId: second.id, + }, + }, + positionsData: new Map(), + selectedToken, + selectedYieldId: null, + yieldOptions: options, + }) + ).toBe(second); + expect( + resolveYield({ + entry: { + ...baseEntry, + preferredTokenYieldsPerNetwork: { + [EvmNetworks.Ethereum]: { + [tokenString(selectedToken.token)]: second.id, + }, + }, + }, + positionsData: new Map(), + selectedToken, + selectedYieldId: null, + yieldOptions: options, + }) + ).toBe(second); + expect( + resolveYield({ + entry: { + ...baseEntry, + preferredTokenYieldsPerNetwork: { + [EvmNetworks.Ethereum]: { + [tokenString(selectedToken.token)]: "*", + }, + }, + }, + positionsData: new Map(), + selectedToken, + selectedYieldId: null, + yieldOptions: options, + }) + ).toBe(second); + }); + + it("resolves validators from retained intent, one-time init, and fallback", () => { + const first = Schema.decodeSync(EarnValidator)( + yieldApiValidatorFixture({ address: "0xfirst", name: "First" }) + ); + const second = Schema.decodeSync(EarnValidator)( + yieldApiValidatorFixture({ address: "0xsecond", name: "Second" }) + ); + const baseEntry = { + categoryOrder: [], + dashboardVariant: false, + initParams: null, + walletResolution: "settled" as const, + walletScope: null, + }; + + expect( + resolveValidators({ + entry: baseEntry, + selectedValidatorKeys: new Set(), + validatorOptions: [], + }) + ).toEqual([]); + expect( + resolveValidators({ + entry: baseEntry, + selectedValidatorKeys: new Set([second.key]), + validatorOptions: [first, second], + }) + ).toEqual([second]); + expect( + resolveValidators({ + entry: { + ...baseEntry, + initParams: { + accountId: null, + balanceId: null, + network: null, + pendingaction: null, + tab: null, + token: null, + validator: "second", + yieldId: null, + }, + }, + selectedValidatorKeys: new Set(), + validatorOptions: [first, second], + }) + ).toEqual([second]); + expect( + resolveValidators({ + entry: baseEntry, + selectedValidatorKeys: new Set(), + validatorOptions: [first, second], + }) + ).toEqual([first]); + }); +}); diff --git a/packages/widget/tests/features/earn-state-machine-view.test.ts b/packages/widget/tests/features/earn-state-machine-view.test.ts new file mode 100644 index 000000000..73c9c2cd4 --- /dev/null +++ b/packages/widget/tests/features/earn-state-machine-view.test.ts @@ -0,0 +1,760 @@ +import { Cause, Option, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { + EarnValidator, + type EarnYield, +} from "../../src/domain/schema/earn-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import type { PositionsData } from "../../src/domain/types/positions"; +import { tokenString } from "../../src/domain/types/tokens"; +import { + availableYieldCategoriesAtom, + earnYieldCatalogAtom, + initYieldAtom, + mergedTokenOptionsAtom, + positionsDataAtom, + yieldValidatorsAtom, +} from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { + AvailableYieldCategoriesKey, + InitYieldKey, + PositionsDataKey, + TokenOptionsKey, + YieldCatalogKey, + YieldValidatorsKey, +} from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { resolveEarnView } from "../../src/features/earn/state/atoms-state/resolver/view"; +import { + EarnCatalogError, + type EarnEntry, + type EarnTokenOption, + makeDefaultEarnIntent, +} from "../../src/features/earn/state/atoms-state/types"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; + +const entry: EarnEntry = { + categoryOrder: ["stake", "defi", "rwa"], + dashboardVariant: true, + initParams: null, + preferredTokenYieldsPerNetwork: null, + tokensForEnabledYieldsOnly: false, + walletResolution: "settled", + walletScope: null, +}; + +const yieldModel = yieldApiYieldFixture(); +const tokenOption = { + amount: "10", + availableYields: [yieldModel.id], + source: "balance" as const, + token: yieldModel.token, +} satisfies EarnTokenOption; +const walletScope = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)("0xwallet"), + network: "ethereum", +}); + +const resolveClassicView = ({ + positionsResult = AsyncResult.success(new Map() as PositionsData), + intent = makeDefaultEarnIntent(), + scope = walletScope as WalletScopeKey | null, + tokenOptionsResult = AsyncResult.success([tokenOption]), + yieldsResult = AsyncResult.success([yieldModel]), +}: { + intent?: ReturnType; + positionsResult?: AsyncResult.AsyncResult; + scope?: WalletScopeKey | null; + tokenOptionsResult?: AsyncResult.AsyncResult< + ReadonlyArray, + EarnCatalogError + >; + yieldsResult?: AsyncResult.AsyncResult< + ReadonlyArray, + EarnCatalogError + >; +}) => { + const selectionSeedYieldId = intent.selectedYieldId; + const classicEntry = { + ...entry, + dashboardVariant: false, + walletScope: scope, + }; + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: selectionSeedYieldId })), + AsyncResult.success(null), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: selectionSeedYieldId, + scope, + tokensForEnabledYieldsOnly: false, + }) + ), + tokenOptionsResult, + ], + [positionsDataAtom(new PositionsDataKey({ scope })), positionsResult], + [ + earnYieldCatalogAtom( + new YieldCatalogKey({ + category: null, + network: yieldModel.token.network, + yieldIds: [yieldModel.id], + }) + ), + yieldsResult, + ], + ], + }); + + return registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry: classicEntry, + intent, + }) + ) + ); +}; + +const resolveRequiredValidatorView = ( + validatorsResult: AsyncResult.AsyncResult< + ReadonlyArray, + EarnCatalogError + > +) => { + const base = yieldApiYieldFixture(); + const requiredYield = { + ...base, + mechanics: { ...base.mechanics, requiresValidatorSelection: true }, + } satisfies EarnYield; + const requiredToken = { + ...tokenOption, + availableYields: [requiredYield.id], + token: requiredYield.token, + } satisfies EarnTokenOption; + const validators = yieldValidatorsAtom( + new YieldValidatorsKey({ + network: requiredYield.token.network, + selectedYieldId: requiredYield.id, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope: walletScope, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.success([requiredToken]), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: walletScope })), + AsyncResult.success(new Map() as PositionsData), + ], + [ + earnYieldCatalogAtom( + new YieldCatalogKey({ + category: null, + network: requiredYield.token.network, + yieldIds: [requiredYield.id], + }) + ), + AsyncResult.success([requiredYield]), + ], + [validators.initialValidatorsResultAtom, validatorsResult], + ], + }); + + return registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry: { ...entry, dashboardVariant: false, walletScope }, + intent: makeDefaultEarnIntent(), + }) + ) + ); +}; + +describe("Earn state machine view", () => { + const refreshFailure = (value: A, error: EarnCatalogError) => + AsyncResult.failure(Cause.fail(error), { + previousSuccess: Option.some(AsyncResult.success(value)), + }); + + it("never enables submission without a connected wallet owner", () => { + const view = resolveClassicView({ scope: null }); + + expect(view.status).toBe("ready"); + expect(view.can.submit).toBe(false); + }); + + it("waits for dashboard category discovery before resolving tokens", () => { + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + availableYieldCategoriesAtom( + new AvailableYieldCategoriesKey({ + categoryOrder: entry.categoryOrder, + network: null, + }) + ), + AsyncResult.initial(true), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope: null, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.success([]), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: null })), + AsyncResult.success(new Map()), + ], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry, + intent: makeDefaultEarnIntent(), + }) + ) + ); + + expect(view.status).toBe("loading-categories"); + expect(view.selection.category).toBeNull(); + expect(view.can).toEqual({ + selectToken: false, + selectValidator: false, + selectYield: false, + submit: false, + }); + }); + + it("publishes no categories after successful empty discovery", () => { + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + availableYieldCategoriesAtom( + new AvailableYieldCategoriesKey({ + categoryOrder: entry.categoryOrder, + network: null, + }) + ), + AsyncResult.success([]), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope: null, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.success([]), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: null })), + AsyncResult.success(new Map()), + ], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry, + intent: makeDefaultEarnIntent(), + }) + ) + ); + + expect(view.status).toBe("no-categories"); + expect(view.selection.category).toBeNull(); + }); + + it("distinguishes first token acquisition failure from no tokens", () => { + const classicEntry = { ...entry, dashboardVariant: false }; + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "token-balances-scan", + }); + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope: null, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.failure(Cause.fail(error)), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: null })), + AsyncResult.success(new Map()), + ], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry: classicEntry, + intent: makeDefaultEarnIntent(), + }) + ) + ); + + expect(view.status).toBe("failed"); + expect(view.failure).toEqual({ + _tag: "ResourceFailure", + error, + stage: "token-options", + }); + }); + + it("keeps the previous token options ready after a refresh failure", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "token-balances-scan", + }); + const view = resolveClassicView({ + tokenOptionsResult: refreshFailure([tokenOption], error), + }); + + expect(view.status).toBe("ready"); + expect(view.failure).toBeNull(); + expect(view.selection.token).toEqual(tokenOption); + expect(view.resources.tokenOptions).toMatchObject({ + items: [tokenOption], + waiting: false, + }); + }); + + it("does not replace an explicit token while token options are still resolving", () => { + const view = resolveClassicView({ + intent: { + ...makeDefaultEarnIntent(), + selectedTokenKey: "ethereum-not-yet-loaded", + }, + tokenOptionsResult: AsyncResult.waiting( + AsyncResult.success([tokenOption]) + ), + }); + + expect(view.status).toBe("loading-token-options"); + expect(view.selection.token).toBeNull(); + expect(view.resources.tokenOptions).toMatchObject({ + items: [tokenOption], + waiting: true, + }); + }); + + it("uses the committed yield as a resource seed after initialization is consumed", () => { + const selectedYield = yieldApiYieldFixture({ + id: "ethereum-eth-native-staking", + }); + const selectedToken = { + ...tokenOption, + availableYields: [selectedYield.id], + token: selectedYield.token, + } satisfies EarnTokenOption; + const intent = { + ...makeDefaultEarnIntent(), + selectedTokenKey: tokenString(selectedToken.token), + selectedYieldId: selectedYield.id, + }; + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: selectedYield.id })), + AsyncResult.success(selectedYield), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: selectedYield.id, + scope: walletScope, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.success([selectedToken]), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: walletScope })), + AsyncResult.success(new Map() as PositionsData), + ], + [ + earnYieldCatalogAtom( + new YieldCatalogKey({ + category: null, + network: selectedYield.token.network, + yieldIds: [selectedYield.id], + }) + ), + AsyncResult.success([selectedYield]), + ], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry: { ...entry, dashboardVariant: false, walletScope }, + intent, + }) + ) + ); + + expect(view.status).toBe("ready"); + expect(view.selection.yield).toEqual(selectedYield); + }); + + it("publishes a blocking category discovery failure", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "available-yield-categories", + }); + const categoryAtom = availableYieldCategoriesAtom( + new AvailableYieldCategoriesKey({ + categoryOrder: entry.categoryOrder, + network: null, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [categoryAtom, AsyncResult.failure(Cause.fail(error))], + [ + positionsDataAtom(new PositionsDataKey({ scope: null })), + AsyncResult.success(new Map()), + ], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry, + intent: makeDefaultEarnIntent(), + }) + ) + ); + + expect(view.status).toBe("failed"); + expect(view.failure).toEqual({ + _tag: "ResourceFailure", + error, + stage: "categories", + }); + expect(view.retryTargetAtom).toBe(categoryAtom); + }); + + it("distinguishes first yield acquisition failure from no yields", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "earn-yield-catalog", + }); + const view = resolveClassicView({ + yieldsResult: AsyncResult.failure(Cause.fail(error)), + }); + + expect(view.status).toBe("failed"); + expect(view.failure).toEqual({ + _tag: "ResourceFailure", + error, + stage: "yields", + }); + }); + + it("keeps the previous yields ready after a refresh failure", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "earn-yield-catalog", + }); + const view = resolveClassicView({ + yieldsResult: refreshFailure([yieldModel], error), + }); + + expect(view.status).toBe("ready"); + expect(view.failure).toBeNull(); + expect(view.selection.yield).toEqual(yieldModel); + expect(view.resources.yields).toEqual({ + items: [yieldModel], + waiting: false, + }); + }); + + it("waits for positions before publishing a connected selection as ready", () => { + const view = resolveClassicView({ + positionsResult: AsyncResult.initial(true), + }); + + expect(view.status).toBe("loading-positions"); + expect(view.can.submit).toBe(false); + expect(view.resources.positions.waiting).toBe(true); + }); + + it("publishes a blocking first positions failure", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "positions-data", + }); + const view = resolveClassicView({ + positionsResult: AsyncResult.failure(Cause.fail(error)), + }); + + expect(view.status).toBe("failed"); + expect(view.failure).toEqual({ + _tag: "ResourceFailure", + error, + stage: "positions", + }); + }); + + it("keeps previous positions usable after a refresh failure", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "positions-data", + }); + const positions = new Map() as PositionsData; + const view = resolveClassicView({ + positionsResult: refreshFailure(positions, error), + }); + + expect(view.status).toBe("ready"); + expect(view.failure).toBeNull(); + }); + + it("publishes no validators when a required validator set loads empty", () => { + const base = yieldApiYieldFixture(); + const requiredYield = { + ...base, + mechanics: { + ...base.mechanics, + requiresValidatorSelection: true, + }, + } satisfies EarnYield; + const requiredToken = { + ...tokenOption, + availableYields: [requiredYield.id], + token: requiredYield.token, + } satisfies EarnTokenOption; + const validators = yieldValidatorsAtom( + new YieldValidatorsKey({ + network: requiredYield.token.network, + selectedYieldId: requiredYield.id, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope: walletScope, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.success([requiredToken]), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: walletScope })), + AsyncResult.success(new Map() as PositionsData), + ], + [ + earnYieldCatalogAtom( + new YieldCatalogKey({ + category: null, + network: requiredYield.token.network, + yieldIds: [requiredYield.id], + }) + ), + AsyncResult.success([requiredYield]), + ], + [validators.initialValidatorsResultAtom, AsyncResult.success([])], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry: { ...entry, dashboardVariant: false, walletScope }, + intent: makeDefaultEarnIntent(), + }) + ) + ); + + expect(view.status).toBe("no-validators"); + expect(view.can.submit).toBe(false); + }); + + it("waits for required validators before publishing readiness", () => { + const base = yieldApiYieldFixture(); + const requiredYield = { + ...base, + mechanics: { + ...base.mechanics, + requiresValidatorSelection: true, + }, + } satisfies EarnYield; + const requiredToken = { + ...tokenOption, + availableYields: [requiredYield.id], + token: requiredYield.token, + } satisfies EarnTokenOption; + const validators = yieldValidatorsAtom( + new YieldValidatorsKey({ + network: requiredYield.token.network, + selectedYieldId: requiredYield.id, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + [ + initYieldAtom(new InitYieldKey({ yieldId: null })), + AsyncResult.success(null), + ], + [ + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope: walletScope, + tokensForEnabledYieldsOnly: false, + }) + ), + AsyncResult.success([requiredToken]), + ], + [ + positionsDataAtom(new PositionsDataKey({ scope: walletScope })), + AsyncResult.success(new Map() as PositionsData), + ], + [ + earnYieldCatalogAtom( + new YieldCatalogKey({ + category: null, + network: requiredYield.token.network, + yieldIds: [requiredYield.id], + }) + ), + AsyncResult.success([requiredYield]), + ], + [validators.initialValidatorsResultAtom, AsyncResult.initial(true)], + ], + }); + const view = registry.get( + Atom.make((context) => + resolveEarnView({ + context, + entry: { ...entry, dashboardVariant: false, walletScope }, + intent: makeDefaultEarnIntent(), + }) + ) + ); + + expect(view.status).toBe("loading-validators"); + expect(view.can.submit).toBe(false); + }); + + it("blocks on first required-validator acquisition failure", () => { + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "validators", + }); + const view = resolveRequiredValidatorView( + AsyncResult.failure(Cause.fail(error)) + ); + + expect(view.status).toBe("failed"); + expect(view.failure).toEqual({ + _tag: "ResourceFailure", + error, + stage: "validators", + }); + }); + + it("keeps previous validators ready after a refresh failure", () => { + const validator = Schema.decodeSync(EarnValidator)( + yieldApiValidatorFixture({ address: "0xvalidator" }) + ); + const error = new EarnCatalogError({ + cause: new Error("offline"), + operation: "validators", + }); + const view = resolveRequiredValidatorView( + AsyncResult.failure(Cause.fail(error), { + previousSuccess: Option.some(AsyncResult.success([validator])), + }) + ); + + expect(view.status).toBe("ready"); + expect(view.failure).toBeNull(); + expect(view.selection.validators).toEqual([validator]); + }); +}); diff --git a/packages/widget/tests/features/earn-validator-search.test.ts b/packages/widget/tests/features/earn-validator-search.test.ts new file mode 100644 index 000000000..8ff978f30 --- /dev/null +++ b/packages/widget/tests/features/earn-validator-search.test.ts @@ -0,0 +1,254 @@ +import { Duration, Effect, Layer } from "effect"; +import { Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import type { EarnYield } from "../../src/domain/schema/earn-models"; +import { + earnValidatorSelectionViewAtom, + selectEarnValidatorAtom, + setEarnValidatorSearchAtom, +} from "../../src/features/earn/facade"; +import { yieldValidatorsAtom } from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { + YieldValidatorsKey, + YieldValidatorsPullKey, +} from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { + type ValidatorDirectoryRequest, + YieldResourceSource, +} from "../../src/services/api/yield-resource-source"; +import { getPullResultItems } from "../../src/shared/effect/pagination"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; + +const yieldId = yieldApiYieldFixture().id; + +const VALIDATOR_SEARCH_DEBOUNCE_MS = 300; +const SLOW_SEARCH_RESPONSE_MS = 1000; + +const validatorSelectionYield = (): typeof EarnYield.Type => { + const base = yieldApiYieldFixture(); + + return { + ...base, + mechanics: { ...base.mechanics, requiresValidatorSelection: true }, + }; +}; + +describe("Earn validator search", () => { + it("searches name and address independently and merges the results", async () => { + const search = "needle"; + const byName = { + ...yieldApiValidatorFixture({ address: "name-match" }), + key: "name-match" as never, + }; + const byAddress = { + ...yieldApiValidatorFixture({ address: "address-match" }), + key: "address-match" as never, + }; + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => { + const getItems = (): Array => { + if (request.name === search && request.address === undefined) { + return [byName]; + } + if (request.address === search && request.name === undefined) { + return [byAddress]; + } + return []; + }; + const items = getItems(); + + return Effect.succeed({ + items, + limit: request.limit, + offset: request.offset, + total: items.length, + }); + }); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ listValidators } as never) + ) + ), + ], + }); + const validators = yieldValidatorsAtom( + new YieldValidatorsKey({ selectedYieldId: yieldId }) + ); + const pull = validators.validatorsPullAtom( + new YieldValidatorsPullKey({ search }) + ); + const unmountLoaded = registry.mount(validators.rememberValidatorsAtom); + const unmount = registry.mount(pull); + + await Effect.runPromise(Effect.yieldNow); + await expect + .poll(() => listValidators.mock.calls.length) + .toBeGreaterThan(0); + const searchRequests = listValidators.mock.calls + .map(([request]) => request) + .filter((request) => request.name || request.address); + + expect( + searchRequests.map(({ address, name }) => ({ address, name })) + ).toEqual([ + { address: undefined, name: search }, + { address: search, name: undefined }, + ]); + + expect( + getPullResultItems(registry.get(pull)) + .flatMap((page) => page.items) + .map((validator) => validator.address) + ).toEqual(["name-match", "address-match"]); + + registry.set(validators.rememberValidatorsAtom, [byAddress as never]); + expect( + [...registry.get(validators.rememberValidatorsAtom).values()].map( + (validator) => validator.address + ) + ).toContain("address-match"); + + unmount(); + unmountLoaded(); + }); + + it("debounces the normalized query, rekeys the request, and drops stale results", async () => { + vi.useFakeTimers(); + const selectedYield = validatorSelectionYield(); + // The stubbed source bypasses decoding, so the derived `key` is supplied + // here the same way the schema derives it from the address. + const validator = (address: string, name: string) => ({ + ...yieldApiValidatorFixture({ address, name }), + key: address as never, + }); + const catalogValidator = validator( + "0x1111111111111111111111111111111111111111", + "Catalog" + ); + const alphaValidator = validator( + "0x2222222222222222222222222222222222222222", + "Alpha" + ); + const betaValidator = validator( + "0x3333333333333333333333333333333333333333", + "Beta" + ); + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => { + const page = (items: ReadonlyArray) => + Effect.succeed({ + items, + limit: request.limit, + offset: request.offset, + total: items.length, + }); + const query = request.name ?? request.address ?? null; + + if (request.preferred) return page([]); + if (query === null) return page([catalogValidator]); + // Only the name branch matches these fixtures. The Alpha response is + // delayed past the point where the query has moved on to Beta. + if (query === "Alpha") { + return Effect.sleep(Duration.millis(SLOW_SEARCH_RESPONSE_MS)).pipe( + Effect.andThen(page(request.name ? [alphaValidator] : [])) + ); + } + if (query === "Beta") return page(request.name ? [betaValidator] : []); + return page([]); + }); + const listYields = vi.fn(() => + Effect.succeed({ + items: [selectedYield], + limit: 100, + offset: 0, + total: 1, + }) + ); + const getTokenOptions = vi.fn(() => + Effect.succeed([ + { + availableYields: [selectedYield.id], + token: selectedYield.token, + }, + ]) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.mergeAll( + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ listValidators, listYields } as never) + ), + Layer.succeed( + LegacyResourceSource, + LegacyResourceSource.of({ getTokenOptions } as never) + ) + ) as never + ), + ], + }); + const unmount = registry.mount(earnValidatorSelectionViewAtom); + const searchQueries = () => + listValidators.mock.calls + .map(([request]) => request.name ?? request.address ?? null) + .filter((query): query is string => query !== null); + const validatorNames = () => + registry + .get(earnValidatorSelectionViewAtom) + .data?.map((validator) => validator.name) ?? null; + + try { + await vi.advanceTimersByTimeAsync(0); + expect(validatorNames()).toEqual(["Catalog"]); + + registry.set(setEarnValidatorSearchAtom, " Alpha "); + expect(registry.get(earnValidatorSelectionViewAtom)).toMatchObject({ + isDebouncing: true, + search: " Alpha ", + }); + + await vi.advanceTimersByTimeAsync(VALIDATOR_SEARCH_DEBOUNCE_MS - 1); + expect(registry.get(earnValidatorSelectionViewAtom).isDebouncing).toBe( + true + ); + expect(searchQueries()).toEqual([]); + + await vi.advanceTimersByTimeAsync(1); + expect(registry.get(earnValidatorSelectionViewAtom).isDebouncing).toBe( + false + ); + expect(searchQueries()).toEqual(["Alpha", "Alpha"]); + + registry.set(setEarnValidatorSearchAtom, "Beta"); + await vi.advanceTimersByTimeAsync(VALIDATOR_SEARCH_DEBOUNCE_MS); + expect(searchQueries()).toEqual(["Alpha", "Alpha", "Beta", "Beta"]); + expect(validatorNames()).toEqual(["Beta"]); + + await vi.advanceTimersByTimeAsync(SLOW_SEARCH_RESPONSE_MS); + expect(validatorNames()).toEqual(["Beta"]); + + const beta = registry.get(earnValidatorSelectionViewAtom).data?.[0]; + registry.set(selectEarnValidatorAtom, beta!.key); + await vi.advanceTimersByTimeAsync(0); + registry.set(setEarnValidatorSearchAtom, ""); + await vi.advanceTimersByTimeAsync(VALIDATOR_SEARCH_DEBOUNCE_MS); + + expect([ + ...registry.get(earnValidatorSelectionViewAtom).selected.values(), + ]).toEqual([beta]); + expect(validatorNames()).toEqual( + expect.arrayContaining(["Beta", "Catalog"]) + ); + } finally { + unmount(); + registry.dispose(); + vi.useRealTimers(); + } + }); +}); diff --git a/packages/widget/tests/features/facade-architecture.test.ts b/packages/widget/tests/features/facade-architecture.test.ts new file mode 100644 index 000000000..03acd7e22 --- /dev/null +++ b/packages/widget/tests/features/facade-architecture.test.ts @@ -0,0 +1,121 @@ +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { dirname, extname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const packageRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url)))); +const sourceRoot = join(packageRoot, "src"); + +const sourceFiles = (directory: string): ReadonlyArray => + readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const path = join(directory, entry.name); + if (entry.isDirectory()) return sourceFiles(path); + return [".ts", ".tsx"].includes(extname(path)) ? [path] : []; + }); + +describe("feature facade architecture", () => { + it("does not retain the aggregate Earn bridge", () => { + const aggregateModel = join( + sourceRoot, + "features/earn/ui/classic/earn-page/state/earn-page-model.tsx" + ); + expect(existsSync(aggregateModel)).toBe(false); + + const source = sourceFiles(sourceRoot) + .map((path) => readFileSync(path, "utf8")) + .join("\n"); + expect(source).not.toContain("useEarnPageModel"); + expect(source).not.toContain("EarnPageModelBinding"); + expect(source).not.toContain("useEarnMachine"); + expect(source).not.toContain("useYieldKycGate"); + expect(source).not.toContain("useYieldValidators"); + expect(source).not.toContain("earn/resources/prices"); + expect(source).not.toContain("earn/resources/yield-insights"); + expect(source).not.toContain("earn/resources/yield-validators"); + expect(source).not.toContain("loadedValidatorsAtom"); + }); + + it("does not retain React navigation or Yield Summary bridges", () => { + const source = sourceFiles(sourceRoot) + .map((path) => readFileSync(path, "utf8")) + .join("\n"); + expect(source).not.toContain("executeWidgetNavigationCommand"); + expect(source).not.toContain('from "../../../../yield-summary/react"'); + expect(source).not.toContain('from "../../../../../yield-summary/react"'); + expect(source).not.toContain("yieldSummaryViewAtom"); + }); + + it("does not retain the React-to-runtime router adapter bridge", () => { + const app = readFileSync(join(sourceRoot, "App.tsx"), "utf8"); + const source = sourceFiles(sourceRoot) + .map((path) => readFileSync(path, "utf8")) + .join("\n"); + + expect(app).not.toContain("createMemoryRouter"); + expect(app).toContain('from "react-router/dom"'); + expect(source).not.toContain("WidgetNavigationAdapter"); + expect(source).not.toContain("widgetNavigationAdapterAtom"); + }); + + it("does not make Position Details read one Atom to discover its facade Atoms", () => { + const adapter = readFileSync( + join( + sourceRoot, + "features/position-details/ui/dashboard/hooks/use-position-details-stake.ts" + ), + "utf8" + ); + expect(adapter).not.toContain("positionDetailsStakeFacadeAtom"); + }); + + it("keeps remaining workflow transitions out of React adapters", () => { + const pendingActions = readFileSync( + join( + sourceRoot, + "features/position-details/ui/classic/hooks/use-pending-actions.ts" + ), + "utf8" + ); + const positionDetails = readFileSync( + join( + sourceRoot, + "features/position-details/ui/classic/hooks/use-position-details.ts" + ), + "utf8" + ); + const positionDetailsStateAdapter = readFileSync( + join(sourceRoot, "features/position-details/ui/classic/state/index.tsx"), + "utf8" + ); + const activityPage = readFileSync( + join( + sourceRoot, + "features/activity/ui/classic/activity-page/hooks/use-activity-page.tsx" + ), + "utf8" + ); + const pendingActionRoute = readFileSync( + join(sourceRoot, "app/routes/state/pending-action-deep-link-route.ts"), + "utf8" + ); + + expect(pendingActions).not.toContain("useEffect"); + expect(pendingActions).not.toContain("useNavigate"); + expect(pendingActions).not.toContain("useStartClassicTransactionFlow"); + expect(pendingActions).not.toContain("useMemo"); + expect(positionDetailsStateAdapter).not.toContain("useMemo"); + expect(positionDetailsStateAdapter).not.toContain( + "reducePositionDetailsWorkflow" + ); + expect(positionDetailsStateAdapter).not.toContain( + "getYieldAmountConstraints" + ); + expect(positionDetails).not.toContain("useState"); + expect(positionDetails).not.toContain("useStartClassicTransactionFlow"); + expect(activityPage).not.toContain("useNavigate"); + expect(activityPage).not.toContain("useStartClassicTransactionFlow"); + expect(pendingActionRoute).not.toContain( + "classic-transaction-flow/model/classic-transaction-flow" + ); + }); +}); diff --git a/packages/widget/tests/features/mount-animation.test.ts b/packages/widget/tests/features/mount-animation.test.ts new file mode 100644 index 000000000..d2294c8b3 --- /dev/null +++ b/packages/widget/tests/features/mount-animation.test.ts @@ -0,0 +1,75 @@ +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { mountAnimationStateAtom } from "../../src/features/mount-animation/public-state"; + +vi.mock("../../src/shared/config/widget-defaults", async (importOriginal) => { + const original = + await importOriginal< + typeof import("../../src/shared/config/widget-defaults") + >(); + + return { + ...original, + config: { + ...original.config, + env: { + ...original.config.env, + isTestMode: false, + }, + }, + }; +}); + +const makeWidgetConfig = (dashboardVariant: boolean) => + normalizeWidgetConfig({ + apiKey: "api-key", + dashboardVariant, + variant: "default", + }); + +describe("mount animation state", () => { + it("tracks page and layout completion in the registry", () => { + const registry = AtomRegistry.make({ + initialValues: [[widgetConfigAtom, makeWidgetConfig(false)]], + }); + + expect(registry.get(mountAnimationStateAtom)).toEqual({ + earnPage: false, + layout: false, + }); + + registry.set(mountAnimationStateAtom, { type: "layout" }); + expect(registry.get(mountAnimationStateAtom)).toEqual({ + earnPage: false, + layout: true, + }); + + registry.set(mountAnimationStateAtom, { type: "all" }); + expect(registry.get(mountAnimationStateAtom)).toEqual({ + earnPage: true, + layout: true, + }); + }); + + it("derives its initial state from the initial widget configuration", () => { + const registry = AtomRegistry.make({ + initialValues: [[widgetConfigAtom, makeWidgetConfig(true)]], + }); + + expect(registry.get(mountAnimationStateAtom)).toEqual({ + earnPage: true, + layout: true, + }); + + registry.set(widgetConfigAtom, makeWidgetConfig(false)); + + expect(registry.get(mountAnimationStateAtom)).toEqual({ + earnPage: true, + layout: true, + }); + }); +}); diff --git a/packages/widget/tests/features/page-cta.dom.test.tsx b/packages/widget/tests/features/page-cta.dom.test.tsx new file mode 100644 index 000000000..59df392a1 --- /dev/null +++ b/packages/widget/tests/features/page-cta.dom.test.tsx @@ -0,0 +1,44 @@ +import { act } from "react"; +import { describe, expect, it, vi } from "vitest"; +import { SKAtomRegistryProvider } from "../../src/app/composition/providers/atom-runtime"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import { PageCtaButton } from "../../src/features/widget-shell/page-cta"; +import { render } from "../utils/test-utils.dom"; + +const settings = normalizeWidgetConfig({ + apiKey: "test-api-key", + variant: "default", +}); + +describe("PageCtaButton", () => { + it("prevents clicks while loading", async () => { + const onClick = vi.fn(); + const renderButton = (isLoading: boolean) => ( + + + + ); + const app = await render(renderButton(true)); + const button = app.container.querySelector("button"); + + expect(button?.disabled).toBe(true); + + act(() => button?.click()); + expect(onClick).not.toHaveBeenCalled(); + + await app.rerender(renderButton(false)); + + expect(button?.disabled).toBe(false); + + act(() => button?.click()); + expect(onClick).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/widget/tests/features/page-workflow-state.dom.test.tsx b/packages/widget/tests/features/page-workflow-state.dom.test.tsx new file mode 100644 index 000000000..1b2f16de3 --- /dev/null +++ b/packages/widget/tests/features/page-workflow-state.dom.test.tsx @@ -0,0 +1,74 @@ +import { useAtom } from "@effect/atom-react"; +import BigNumber from "bignumber.js"; +import { Schema } from "effect"; +import { act, type ReactNode } from "react"; +import { describe, expect, it } from "vitest"; +import { SKAtomRegistryProvider } from "../../src/app/composition/providers/atom-runtime"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + PositionDetailsWorkflowKey, + positionDetailsWorkflowAtom, + reducePositionDetailsWorkflow, +} from "../../src/features/position-details/state/workflow"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { render } from "../utils/test-utils.dom"; + +const settings = normalizeWidgetConfig({ + apiKey: "test-api-key", + variant: "default", +}); + +const positionWorkflowAtom = positionDetailsWorkflowAtom( + new PositionDetailsWorkflowKey({ + balanceId: "balance-1", + integrationId: "yield-1", + pendingActionType: null, + scope: new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)("0xwallet-a"), + network: "ethereum", + }), + }) +); + +const PositionAmountHarness = () => { + const [workflow, setWorkflow] = useAtom(positionWorkflowAtom); + + return ( + <> + {workflow.unstakeAmount.toFixed()} + + + ); +}; + +const renderWorkflow = (children: ReactNode) => + render( + + {children} + + ); + +describe("page workflow atom adapters", () => { + it("updates the visible position amount through the workflow atom", async () => { + const app = await renderWorkflow(); + + expect(app.container.querySelector("output")?.textContent).toBe("0"); + await act(async () => app.container.querySelector("button")?.click()); + expect(app.container.querySelector("output")?.textContent).toBe("5"); + }); +}); diff --git a/packages/widget/tests/features/position-details-exit-command.test.ts b/packages/widget/tests/features/position-details-exit-command.test.ts new file mode 100644 index 000000000..469f18aad --- /dev/null +++ b/packages/widget/tests/features/position-details-exit-command.test.ts @@ -0,0 +1,193 @@ +import BigNumber from "bignumber.js"; +import { Effect, Layer, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { EarnBalance } from "../../src/domain/schema/earn-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { classicFlowSessionStore } from "../../src/features/classic-transaction-flow/facade"; +import { + PositionBalancesKey, + positionBalancesAtom, + positionBalancesByTypeAtom, +} from "../../src/features/portfolio/resources/positions"; +import { + setPositionDetailsExitMaxAmountAtom, + submitPositionDetailsExitAtom, +} from "../../src/features/position-details/state/classic-flow-actions"; +import { + PositionDetailsWorkflowKey, + positionDetailsWorkflowAtom, +} from "../../src/features/position-details/state/workflow"; +import { walletConnectionStateAtom } from "../../src/features/wallet/public-state"; +import { + YieldOpportunityKey, + yieldOpportunityAtom, +} from "../../src/resources/yield-opportunity/provider"; +import { + WidgetNavigation, + type WidgetPath, +} from "../../src/services/navigation/widget-navigation"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture, yieldBalanceFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x1234567890123456789012345678901234567890" +); +const scope = new WalletScopeKey({ address, network: "ethereum" }); +const baseYield = yieldApiYieldFixture(); +const selectedYield = yieldApiYieldFixture({ + metadata: { + ...baseYield.metadata, + supportedStandards: ["ERC4626"], + }, +}); +const balance = Schema.decodeUnknownSync(EarnBalance)( + yieldBalanceFixture({ + address, + amount: "1", + token: selectedYield.token, + }) +); +const workflowKey = new PositionDetailsWorkflowKey({ + balanceId: "balance-1", + integrationId: selectedYield.id, + pendingActionType: null, + scope, +}); +const positionKey = new PositionBalancesKey({ + balanceId: workflowKey.balanceId, + scope, + yieldId: selectedYield.id, +}); + +const makeRegistry = ({ + push, + trackEvent, +}: { + readonly push: ReturnType void>>; + readonly trackEvent: TrackingService["Service"]["trackEvent"]; +}) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.mergeAll( + Layer.succeed( + WidgetNavigation, + WidgetNavigation.of({ + back: () => Effect.void, + push: (path) => Effect.sync(() => push(path)), + replace: () => Effect.void, + }) + ), + Layer.succeed( + TrackingService, + TrackingService.of({ + trackEvent, + trackPageView: () => Effect.void, + }) + ) + ) as never + ), + Atom.initialValue(walletConnectionStateAtom, { + additionalAddresses: null, + address, + chain: {} as never, + connector: {} as never, + connectorChains: [], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum", + status: "connected", + } as const), + Atom.initialValue( + yieldOpportunityAtom( + new YieldOpportunityKey({ yieldId: selectedYield.id }) + ), + AsyncResult.success(selectedYield) + ), + Atom.initialValue( + positionBalancesAtom(positionKey), + AsyncResult.success({ + balances: [balance], + rewardRate: null, + type: "default" as const, + }) + ), + Atom.initialValue( + positionBalancesByTypeAtom(positionKey), + AsyncResult.success( + new Map([ + ["active", [{ ...balance, tokenPriceInUsd: new BigNumber(1) }]], + ]) + ) + ), + ], + }); + +describe("Position Details exit command", () => { + it("submits the displayed partial amount for an ERC-4626 exit", async () => { + const push = vi.fn<(path: WidgetPath) => void>(); + const trackEvent = vi.fn( + () => Effect.void + ); + const registry = makeRegistry({ push, trackEvent }); + + try { + registry.set(positionDetailsWorkflowAtom(workflowKey), { + pendingActions: new Map(), + unstakeAmount: new BigNumber("0.4"), + unstakeUseMaxAmount: false, + }); + registry.set(submitPositionDetailsExitAtom(workflowKey), undefined); + + await vi.waitFor(() => expect(push).toHaveBeenCalledOnce()); + expect(push).toHaveBeenCalledWith( + `/positions/${selectedYield.id}/balance-1/unstake/review` + ); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom)?.intake + ).toMatchObject({ + _tag: "Exit", + request: { + arguments: { + amount: "0.4", + }, + }, + unstakeAmount: new BigNumber("0.4"), + }); + } finally { + registry.dispose(); + } + }); + + it("sets the displayed maximum and tracks the user intent", async () => { + const push = vi.fn<(path: WidgetPath) => void>(); + const trackEvent = vi.fn( + () => Effect.void + ); + const registry = makeRegistry({ push, trackEvent }); + + try { + registry.set(setPositionDetailsExitMaxAmountAtom(workflowKey), undefined); + + await vi.waitFor(() => expect(trackEvent).toHaveBeenCalledOnce()); + expect(registry.get(positionDetailsWorkflowAtom(workflowKey))).toEqual({ + pendingActions: new Map(), + unstakeAmount: new BigNumber(1), + unstakeUseMaxAmount: true, + }); + expect(trackEvent).toHaveBeenCalledWith("positionDetailsPageMaxClicked", { + yieldId: selectedYield.id, + }); + expect(push).not.toHaveBeenCalled(); + } finally { + registry.dispose(); + } + }); +}); diff --git a/packages/widget/tests/features/position-details-workflow.test.ts b/packages/widget/tests/features/position-details-workflow.test.ts new file mode 100644 index 000000000..0f42f02d6 --- /dev/null +++ b/packages/widget/tests/features/position-details-workflow.test.ts @@ -0,0 +1,87 @@ +import BigNumber from "bignumber.js"; +import { Schema } from "effect"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + makePositionDetailsWorkflowState, + PositionDetailsWorkflowKey, + positionDetailsWorkflowAtom, + reducePositionDetailsWorkflow, +} from "../../src/features/position-details/state/workflow"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; + +const scopeA = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ), + network: "ethereum", +}); +const scopeB = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" + ), + network: "ethereum", +}); + +const workflowAtom = positionDetailsWorkflowAtom( + new PositionDetailsWorkflowKey({ + balanceId: "balance-1", + integrationId: "yield-1", + pendingActionType: null, + scope: scopeA, + }) +); + +describe("position details workflow atoms", () => { + it("owns amount transitions and resets in the registry", () => { + const registry = AtomRegistry.make(); + const changed = reducePositionDetailsWorkflow({ + action: { type: "unstake/amount/change", data: new BigNumber(2) }, + maxUnstakeAmount: new BigNumber(10), + state: registry.get(workflowAtom), + }); + + registry.set(workflowAtom, changed); + expect(registry.get(workflowAtom).unstakeAmount.toFixed()).toBe("2"); + expect(registry.get(workflowAtom).unstakeUseMaxAmount).toBe(false); + + const maximum = reducePositionDetailsWorkflow({ + action: { type: "unstake/amount/max" }, + maxUnstakeAmount: new BigNumber(10), + state: registry.get(workflowAtom), + }); + registry.set(workflowAtom, maximum); + + expect(registry.get(workflowAtom).unstakeAmount.toFixed()).toBe("10"); + expect(registry.get(workflowAtom).unstakeUseMaxAmount).toBe(true); + + registry.set( + workflowAtom, + makePositionDetailsWorkflowState(new BigNumber(1)) + ); + + expect(registry.get(workflowAtom).unstakeAmount.toFixed()).toBe("1"); + expect(registry.get(workflowAtom).pendingActions.size).toBe(0); + }); + + it("selects fresh action state when only the wallet owner changes", () => { + const registry = AtomRegistry.make(); + registry.set( + workflowAtom, + makePositionDetailsWorkflowState(new BigNumber(5)) + ); + const walletBWorkflow = positionDetailsWorkflowAtom( + new PositionDetailsWorkflowKey({ + balanceId: "balance-1", + integrationId: "yield-1", + pendingActionType: null, + scope: scopeB, + }) + ); + + expect(walletBWorkflow).not.toBe(workflowAtom); + expect(registry.get(walletBWorkflow).unstakeAmount.toFixed()).toBe("0"); + expect(registry.get(walletBWorkflow).pendingActions.size).toBe(0); + }); +}); diff --git a/packages/widget/tests/features/semantic-invalidation.test.ts b/packages/widget/tests/features/semantic-invalidation.test.ts new file mode 100644 index 000000000..e500866c5 --- /dev/null +++ b/packages/widget/tests/features/semantic-invalidation.test.ts @@ -0,0 +1,583 @@ +import { Effect, Layer, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import * as Reactivity from "effect/unstable/reactivity/Reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { Integration } from "../../src/domain/borrow/integration"; +import { Market } from "../../src/domain/borrow/market"; +import { BorrowAccountPosition } from "../../src/domain/borrow/position"; +import { EarnLegacyTokenOptionsResponse } from "../../src/domain/schema/earn-models"; +import { + TokenBalancesResponse, + type YieldBalancesCommand, +} from "../../src/domain/schema/financial-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import { + ActivityFilterOptionsKey, + activityFilterOptionsAtom, +} from "../../src/features/activity/resources/activity-actions"; +import { + ActivityActionsKey, + getActivityHistoryKey, +} from "../../src/features/activity/resources/activity-requests"; +import { + BorrowPositionKey, + borrowPositionAtom, +} from "../../src/features/borrow/atoms/resources"; +import { + mergedTokenOptionsAtom, + positionsDataAtom, +} from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { + PositionsDataKey, + TokenOptionsKey, +} from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { activityHistoryPullAtom } from "../../src/resources/activity-history/activity-history"; +import { BorrowResourceSource } from "../../src/services/api/borrow-resource-source"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { + type YieldDirectoryRequest, + YieldResourceSource, +} from "../../src/services/api/yield-resource-source"; +import { ActivityInvalidationKey } from "../../src/services/resource-invalidation"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { + BorrowTransactionWorkflowInput, + ClassicTransactionWorkflowInput, +} from "../../src/services/workflow/transaction-workflow-model"; +import { getTransactionWorkflowInvalidationKeys } from "../../src/services/workflow/transaction-workflow-operations-service"; +import { getPullResultItems } from "../../src/shared/effect/pagination"; +import { yieldApiActionFixture, yieldApiYieldFixture } from "../fixtures"; + +const address = (suffix: string) => + Schema.decodeSync(WalletAddress)(`0x${suffix.padStart(40, "0")}`); +const scopeA = new WalletScopeKey({ + address: address("1"), + network: "ethereum", +}); +const scopeB = new WalletScopeKey({ + address: address("2"), + network: "ethereum", +}); + +const getActivityActionViews = ( + result: Atom.Type> +) => getPullResultItems(result).flatMap((batch) => batch.actions); +const sameWalletCachedScope = new WalletScopeKey({ + additionalAddresses: { + lidoStakeAccounts: ["cached-lido-account"], + stakeAccounts: ["cached-stake-account"], + }, + address: Schema.decodeSync(WalletAddress)( + "0x00000000000000000000000000000000000000ab" + ), + network: "ethereum", +}); +const sameAddressOtherNetworkScope = new WalletScopeKey({ + address: sameWalletCachedScope.address, + network: "base", +}); +const yieldDto = yieldApiYieldFixture(); +const yieldId = Schema.decodeSync(YieldId)(yieldDto.id); +const borrowIntegration = Schema.decodeUnknownSync(Integration)({ + actions: [], + id: "aave-borrow", + metadata: { + description: "Aave lending and borrowing", + externalLink: "https://aave.com", + logoURI: "https://assets.stakek.it/protocols/aave.svg", + }, + name: "Aave V3", + networks: ["ethereum"], + providerId: "aave", +}); +const borrowMarket = Schema.decodeUnknownSync(Market)({ + availableLiquidity: "500000", + availableLiquidityRaw: "500000000000", + borrowRate: "0.06", + collateralTokens: [ + { + liquidationPenalty: "0.05", + liquidationThreshold: "0.85", + maxLtv: "0.8", + priceUsd: "2000", + supplyRate: "0.02", + token: { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + decimals: 18, + name: "Wrapped Ether", + symbol: "WETH", + }, + }, + ], + feeWrapperAddress: null, + id: "aave-v3-ethereum-usdc", + integrationId: borrowIntegration.id, + isBorrowEnabled: true, + loanToken: { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + decimals: 6, + name: "USD Coin", + symbol: "USDC", + }, + loanTokenPriceUsd: "1", + minLoan: null, + network: "ethereum", + poolAddress: "0x0000000000000000000000000000000000000001", + supplyCollateralFeeBps: "0", + totalBorrow: "500000", + totalBorrowRaw: "500000000000", + totalSupply: "1000000", + totalSupplyRaw: "1000000000000", + type: "pool", + utilizationRate: "0.5", +}); +const borrowAccountPosition = (updated: boolean) => + Schema.decodeUnknownSync(BorrowAccountPosition)({ + address: scopeA.address, + availableToBorrowUsd: updated ? "750" : "450", + currentLtv: updated ? "0.1" : "0.4", + debtBalances: [ + { + apy: "0.06", + balance: updated ? "100" : "400", + balanceRaw: updated ? "100000000" : "400000000", + balanceUsd: updated ? "100" : "400", + marketId: borrowMarket.id, + pendingActions: updated + ? [] + : [ + { + args: { + marketId: borrowMarket.id, + tokenAddress: borrowMarket.loanToken.address, + }, + label: "Repay", + type: "repay", + }, + ], + tokenAddress: borrowMarket.loanToken.address, + tokenSymbol: "USDC", + }, + ], + healthFactor: updated ? "8" : "2.125", + integrationId: borrowIntegration.id, + netApy: "-0.006", + netWorthUsd: updated ? "900" : "600", + network: "ethereum", + supplyBalances: [], + totalBorrowedUsd: updated ? "100" : "400", + totalCollateralUsd: "0", + totalSuppliedUsd: "0", + }); +const reactivityAtom = appRuntime.atom( + Effect.gen(function* () { + return yield* Reactivity.Reactivity; + }) +); + +const workflowInvalidationKeys = (scope: WalletScopeKey) => + getTransactionWorkflowInvalidationKeys( + new ClassicTransactionWorkflowInput({ + actionMeta: { + actionId: "completed-action", + address: scope.address, + } as never, + transactions: [], + walletScope: scope, + yieldId, + }) + ); + +describe("semantic resource invalidation", () => { + it("refreshes idle Earn bases for the workflow wallet scope only", async () => { + const versions = new Map([ + [sameWalletCachedScope.address.toLowerCase(), "1"], + [scopeB.address.toLowerCase(), "2"], + ]); + const balanceCalls = new Map(); + const positionCalls = new Map(); + const getLegacyTokenOptions = vi.fn(() => + Effect.succeed( + Schema.decodeUnknownSync(EarnLegacyTokenOptionsResponse)([ + { availableYields: [yieldId], token: yieldDto.token }, + ]) + ) + ); + const scanTokenBalances = vi.fn( + ({ + addresses: { address: walletAddress }, + }: { + readonly addresses: { readonly address: WalletAddress }; + }) => { + balanceCalls.set( + walletAddress, + (balanceCalls.get(walletAddress) ?? 0) + 1 + ); + return Effect.succeed( + Schema.decodeUnknownSync(TokenBalancesResponse)([ + { + amount: versions.get(walletAddress.toLowerCase()) ?? "0", + availableYields: [yieldId], + token: yieldDto.token, + }, + ]) + ); + } + ); + const getPositions = vi.fn((command: YieldBalancesCommand) => { + const walletAddress = command.queries[0]!.address; + positionCalls.set( + walletAddress, + (positionCalls.get(walletAddress) ?? 0) + 1 + ); + return Effect.succeed({ errors: [], items: [] }); + }); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.mergeAll( + Reactivity.layer, + Layer.succeed(LegacyResourceSource, { + getTokenOptions: getLegacyTokenOptions, + scanTokenBalances, + } as never), + Layer.succeed(YieldResourceSource, { getPositions } as never) + ) as never + ), + ], + }); + const tokenOptions = (scope: WalletScopeKey) => + mergedTokenOptionsAtom( + new TokenOptionsKey({ + category: null, + initToken: null, + initTokenNetwork: null, + initYieldId: null, + scope, + tokensForEnabledYieldsOnly: false, + }) + ); + const positions = (scope: WalletScopeKey) => + positionsDataAtom(new PositionsDataKey({ scope })); + const aTokens = tokenOptions(sameWalletCachedScope); + const bTokens = tokenOptions(scopeB); + const aPositions = positions(sameWalletCachedScope); + const bPositions = positions(scopeB); + const unmountA = [registry.mount(aTokens), registry.mount(aPositions)]; + const unmountB = [registry.mount(bTokens), registry.mount(bPositions)]; + const unmountReactivity = registry.mount(reactivityAtom); + + await vi.waitFor(() => { + expect(AsyncResult.getOrThrow(registry.get(aTokens))[0]?.amount).toBe( + "1" + ); + expect(AsyncResult.getOrThrow(registry.get(bTokens))[0]?.amount).toBe( + "2" + ); + expect(positionCalls).toEqual( + new Map([ + [sameWalletCachedScope.address, 1], + [scopeB.address, 1], + ]) + ); + expect(AsyncResult.isSuccess(registry.get(reactivityAtom))).toBe(true); + }); + unmountA.forEach((unmount) => unmount()); + versions.set(sameWalletCachedScope.address.toLowerCase(), "10"); + const reactivity = AsyncResult.getOrThrow(registry.get(reactivityAtom)); + + await Effect.runPromise( + reactivity.withBatch( + reactivity.invalidate(workflowInvalidationKeys(sameWalletCachedScope)) + ) + ); + await vi.waitFor(() => { + expect(balanceCalls.get(sameWalletCachedScope.address)).toBe(2); + expect(positionCalls.get(sameWalletCachedScope.address)).toBe(2); + }); + + const remountA = [registry.mount(aTokens), registry.mount(aPositions)]; + await vi.waitFor(() => + expect(AsyncResult.getOrThrow(registry.get(aTokens))[0]?.amount).toBe( + "10" + ) + ); + expect(balanceCalls.get(scopeB.address)).toBe(1); + expect(positionCalls.get(scopeB.address)).toBe(1); + + remountA.forEach((unmount) => unmount()); + unmountB.forEach((unmount) => unmount()); + unmountReactivity(); + registry.dispose(); + }); + + it("refreshes Activity Pull and bounded filter counts from the first page", async () => { + let version = 1; + const activityRequests: Array<{ + address: WalletAddress; + limit: number; + network: string; + offset: number; + yieldTypes?: ReadonlyArray; + }> = []; + const getActivityActions = vi.fn( + (request: { + readonly address: WalletAddress; + readonly limit: number; + readonly network: string; + readonly offset: number; + readonly yieldTypes?: ReadonlyArray; + }) => { + activityRequests.push({ + address: request.address, + limit: request.limit, + network: request.network, + offset: request.offset, + yieldTypes: request.yieldTypes, + }); + if (request.limit === 1) { + return Effect.succeed({ + items: [], + limit: 1, + offset: 0, + total: version === 1 ? 2 : 1, + }); + } + + const getId = () => { + if (version === 2) return "updated-action"; + if (request.offset === 0) return "old-action-1"; + return "old-action-2"; + }; + const id = getId(); + return Effect.succeed({ + items: [yieldApiActionFixture({ id, yieldId })], + limit: version === 1 ? 1 : 50, + offset: request.offset, + total: version === 1 ? 2 : 1, + }); + } + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.mergeAll( + Reactivity.layer, + Layer.succeed(YieldResourceSource, { + getOpportunity: () => Effect.succeed(yieldDto), + getProvider: () => Effect.fail("no provider"), + listActivity: getActivityActions, + listYields: ({ limit, offset }: YieldDirectoryRequest) => + Effect.succeed({ + items: [yieldDto], + limit, + offset, + total: 1, + }), + } as never) + ) as never + ), + ], + }); + const historyKey = (scope: WalletScopeKey) => + getActivityHistoryKey(new ActivityActionsKey({ filter: "all", scope }))!; + const actions = activityHistoryPullAtom(historyKey(sameWalletCachedScope)); + const filters = activityFilterOptionsAtom( + new ActivityFilterOptionsKey({ scope: sameWalletCachedScope }) + ); + const otherWalletActions = activityHistoryPullAtom(historyKey(scopeB)); + const otherNetworkActions = activityHistoryPullAtom( + historyKey(sameAddressOtherNetworkScope) + ); + const unmountActions = registry.mount(actions); + const unmountFilters = registry.mount(filters); + const unmountOtherWalletActions = registry.mount(otherWalletActions); + const unmountOtherNetworkActions = registry.mount(otherNetworkActions); + const unmountReactivity = registry.mount(reactivityAtom); + + await vi.waitFor(() => { + expect( + getActivityActionViews(registry.get(actions)).map(({ id }) => id) + ).toEqual(["old-action-1"]); + expect( + getActivityActionViews(registry.get(otherWalletActions)).map( + ({ id }) => id + ) + ).toEqual(["old-action-1"]); + expect( + getActivityActionViews(registry.get(otherNetworkActions)).map( + ({ id }) => id + ) + ).toEqual(["old-action-1"]); + }); + registry.set(actions, undefined); + registry.set(otherWalletActions, undefined); + registry.set(otherNetworkActions, undefined); + await vi.waitFor(() => { + expect( + getActivityActionViews(registry.get(actions)).map(({ id }) => id) + ).toEqual(["old-action-1", "old-action-2"]); + expect( + getActivityActionViews(registry.get(otherWalletActions)).map( + ({ id }) => id + ) + ).toEqual(["old-action-1", "old-action-2"]); + expect( + getActivityActionViews(registry.get(otherNetworkActions)).map( + ({ id }) => id + ) + ).toEqual(["old-action-1", "old-action-2"]); + }); + await vi.waitFor(() => + expect(AsyncResult.isSuccess(registry.get(filters))).toBe(true) + ); + expect( + activityRequests + .filter( + ({ address: requestAddress, limit, network }) => + requestAddress === sameWalletCachedScope.address && + network === sameWalletCachedScope.network && + limit > 1 + ) + .every(({ yieldTypes }) => yieldTypes === undefined) + ).toBe(true); + const countRequestsBefore = activityRequests.filter( + ({ address: requestAddress, network }) => + requestAddress === sameWalletCachedScope.address && + network === sameWalletCachedScope.network + ).length; + const otherWalletRequestsBefore = activityRequests.filter( + ({ address: requestAddress }) => requestAddress === scopeB.address + ).length; + const otherNetworkRequestsBefore = activityRequests.filter( + ({ address: requestAddress, network }) => + requestAddress === sameAddressOtherNetworkScope.address && + network === sameAddressOtherNetworkScope.network + ).length; + version = 2; + const reactivity = AsyncResult.getOrThrow(registry.get(reactivityAtom)); + + await Effect.runPromise( + reactivity.invalidate([ + new ActivityInvalidationKey({ scope: sameWalletCachedScope }), + ]) + ); + await vi.waitFor(() => + expect( + getActivityActionViews(registry.get(actions)).map(({ id }) => id) + ).toEqual(["updated-action"]) + ); + expect( + activityRequests.filter( + ({ address: requestAddress, network }) => + requestAddress === sameWalletCachedScope.address && + network === sameWalletCachedScope.network + ).length + ).toBeGreaterThan(countRequestsBefore); + expect( + activityRequests.filter( + ({ address: requestAddress }) => requestAddress === scopeB.address + ) + ).toHaveLength(otherWalletRequestsBefore); + expect( + activityRequests.filter( + ({ address: requestAddress, network }) => + requestAddress === sameAddressOtherNetworkScope.address && + network === sameAddressOtherNetworkScope.network + ) + ).toHaveLength(otherNetworkRequestsBefore); + + unmountActions(); + unmountFilters(); + unmountOtherWalletActions(); + unmountOtherNetworkActions(); + unmountReactivity(); + registry.dispose(); + }); + + it("refreshes the canonical Borrow base and its mounted detail projection", async () => { + let updated = false; + const getIntegrations = vi.fn(() => Effect.succeed([borrowIntegration])); + const getMarkets = vi.fn(() => + Effect.succeed({ + items: [borrowMarket], + limit: 100, + offset: 0, + total: 1, + }) + ); + const getPositionData = vi.fn(() => + Effect.succeed([ + { + integration: borrowIntegration, + position: borrowAccountPosition(updated), + }, + ]) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.mergeAll( + Reactivity.layer, + Layer.succeed(BorrowResourceSource, { + getIntegrations, + getMarkets, + getPositionData, + } as never) + ) as never + ), + ], + }); + const detail = borrowPositionAtom( + new BorrowPositionKey({ + marketId: borrowMarket.id, + scope: scopeA, + }) + ); + const unmountDetail = registry.mount(detail); + const unmountReactivity = registry.mount(reactivityAtom); + + await vi.waitFor(() => { + const value = AsyncResult.getOrThrow(registry.get(detail)); + expect(value.debtBalance?.balance).toBe(400); + expect(value.debtBalance?.pendingActions).toHaveLength(1); + }); + updated = true; + const action = { + action: "repay", + address: scopeA.address, + createdAt: "2026-07-23T00:00:00.000Z", + currentStep: 1, + hasNextStep: false, + id: "borrow-action", + integrationId: borrowIntegration.id, + status: "SUCCESS", + totalSteps: 1, + transactions: [], + } as never; + const keys = getTransactionWorkflowInvalidationKeys( + new BorrowTransactionWorkflowInput({ action, walletScope: scopeA }) + ); + const reactivity = AsyncResult.getOrThrow(registry.get(reactivityAtom)); + + await Effect.runPromise(reactivity.withBatch(reactivity.invalidate(keys))); + await vi.waitFor(() => { + const value = AsyncResult.getOrThrow(registry.get(detail)); + expect(value.debtBalance?.balance).toBe(100); + expect(value.debtBalance?.pendingActions).toHaveLength(0); + }); + expect(getIntegrations).toHaveBeenCalledOnce(); + expect(getMarkets).toHaveBeenCalledTimes(2); + expect(getPositionData).toHaveBeenCalledTimes(2); + + unmountDetail(); + unmountReactivity(); + registry.dispose(); + }); +}); diff --git a/packages/widget/tests/features/wallet-scope-route.dom.test.tsx b/packages/widget/tests/features/wallet-scope-route.dom.test.tsx new file mode 100644 index 000000000..d4c2afa2c --- /dev/null +++ b/packages/widget/tests/features/wallet-scope-route.dom.test.tsx @@ -0,0 +1,184 @@ +import { Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { act, createContext, useContext, useState } from "react"; +import { MemoryRouter, Route, Routes } from "react-router"; +import type { Chain } from "viem"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + useWalletScopeRoute, + WalletScopeRoute, +} from "../../src/features/wallet/react/wallet-scope-route"; +import type { NormalizedWalletState } from "../../src/services/wallet/domain/state"; +import { disconnectedNormalizedWalletState } from "../../src/services/wallet/domain/state"; +import { render } from "../utils/test-utils.dom"; + +const firstAddress = Schema.decodeSync(WalletAddress)("0xwallet-a"); +const secondAddress = Schema.decodeSync(WalletAddress)("0xwallet-b"); +const connectedWalletState = ({ + additionalAddresses = null, + address = firstAddress, +}: { + readonly additionalAddresses?: Extract< + NormalizedWalletState, + { readonly status: "connected" } + >["additionalAddresses"]; + readonly address?: typeof WalletAddress.Type; +} = {}): NormalizedWalletState => ({ + additionalAddresses, + address, + chain: {} as Chain, + connector: {} as Connector, + connectorChains: [], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum", + status: "connected", +}); + +const WalletScopeProbe = () => { + const scope = useWalletScopeRoute(); + + return
{scope.address}
; +}; + +const ReplaceWalletContext = createContext< + (state: NormalizedWalletState) => void +>(() => undefined); + +const WalletScopeReplacement = ({ + state, +}: { + state: NormalizedWalletState; +}) => { + const replaceWallet = useContext(ReplaceWalletContext); + + return ( + + ); +}; + +const TestRouter = ({ + initialResult, + replacement, +}: { + readonly initialResult: AsyncResult.AsyncResult; + readonly replacement?: NormalizedWalletState; +}) => { + const [result, setResult] = useState(initialResult); + + return ( + setResult(AsyncResult.success(state))} + > + + + + } + > + + + {replacement ? ( + + ) : null} + + } + /> + + safe} /> + + + + ); +}; + +const renderRoute = ( + result: AsyncResult.AsyncResult, + replacement?: NormalizedWalletState +) => render(); + +describe("wallet scope route", () => { + it("provides a concrete wallet scope to protected content", async () => { + const app = await renderRoute(AsyncResult.success(connectedWalletState())); + + expect( + app.container.querySelector('[data-testid="scope"]')?.textContent + ).toBe(firstAddress); + }); + + it("redirects a disconnected wallet to the route fallback", async () => { + const app = await renderRoute( + AsyncResult.success(disconnectedNormalizedWalletState) + ); + + await vi.waitFor(() => { + expect( + app.container.querySelector('[data-testid="safe"]')?.textContent + ).toBe("safe"); + }); + }); + + it("waits while the wallet scope is resolving", async () => { + const app = await renderRoute( + AsyncResult.waiting( + AsyncResult.success(disconnectedNormalizedWalletState) + ) + ); + + expect(app.container.textContent).toBe(""); + }); + + it("redirects when the wallet owner changes", async () => { + const app = await renderRoute( + AsyncResult.success(connectedWalletState()), + connectedWalletState({ address: secondAddress }) + ); + + await act(async () => { + app.container + .querySelector('[data-testid="replace-wallet"]') + ?.click(); + }); + await vi.waitFor(() => { + expect( + app.container.querySelector('[data-testid="safe"]')?.textContent + ).toBe("safe"); + }); + }); + + it("keeps the route mounted when additional addresses refresh", async () => { + const app = await renderRoute( + AsyncResult.success(connectedWalletState()), + connectedWalletState({ + additionalAddresses: { binanceBeaconAddress: "bnb-refreshed" }, + }) + ); + + await act(async () => { + app.container + .querySelector('[data-testid="replace-wallet"]') + ?.click(); + }); + + expect( + app.container.querySelector('[data-testid="scope"]')?.textContent + ).toBe(firstAddress); + expect(app.container.querySelector('[data-testid="safe"]')).toBeNull(); + }); +}); diff --git a/packages/widget/tests/features/yield-entry.test.ts b/packages/widget/tests/features/yield-entry.test.ts new file mode 100644 index 000000000..f20b69d4d --- /dev/null +++ b/packages/widget/tests/features/yield-entry.test.ts @@ -0,0 +1,541 @@ +import BigNumber from "bignumber.js"; +import { Effect, Layer, Option, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { applicationRouterAtom } from "../../src/app/runtime/application-router-runtime"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { classicFlowSessionStore } from "../../src/features/classic-transaction-flow/facade"; +import { makeClassicTransactionFlowDestination } from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { + getYieldEntryCta, + makeYieldEntry, + type YieldEntryFacadeInput, +} from "../../src/features/yield-entry"; +import { WidgetNavigation } from "../../src/services/navigation/widget-navigation"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { WalletModal } from "../../src/services/wallet/wallet-modal"; +import { yieldApiYieldFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x1234567890123456789012345678901234567890" +); +const walletScope = new WalletScopeKey({ + address, + network: "ethereum", +}); + +const makeFacadeInput = ( + override: Partial = {} +): YieldEntryFacadeInput => { + const selectedYield = yieldApiYieldFixture(); + return { + availableAmount: new BigNumber(10), + canSubmit: true, + connected: true, + defaultToMinimum: false, + destination: makeClassicTransactionFlowDestination({ routeBase: "" }), + entry: { + amount: new BigNumber(1), + selectedProviderYieldId: null, + token: selectedYield.token, + tronResource: null, + useMaxAmount: false, + validators: new Map(), + yield: selectedYield, + }, + externalProviders: false, + hasNoYields: false, + isAppLoading: false, + isFetching: false, + isKycBlocking: false, + isKycLoading: false, + isLedgerAccountPlaceholder: false, + isOwnerCurrent: true, + isWalletConnecting: false, + positionsData: new Map(), + providers: [ + { + logo: undefined, + name: "Provider", + rewardRate: 0.12, + rewardRateFormatted: "12%", + rewardType: "apy", + }, + ], + submitted: false, + validateAmount: true, + wallet: { + additionalAddresses: null, + address, + isLedgerLive: false, + }, + walletScope, + ...override, + }; +}; + +/** + * Observable stand-ins for every port a submission may reach, so a test can + * assert both the effects a branch performs and the ones it must not. + */ +const makeObservablePorts = () => { + const closeChain = vi.fn(); + const openConnect = vi.fn(); + const push = vi.fn(() => Effect.void); + const replace = vi.fn(() => Effect.void); + const trackEvent = vi.fn(() => Effect.void); + + return { + closeChain, + layer: Layer.mergeAll( + Layer.succeed( + WalletModal, + WalletModal.of({ + closeChain: Effect.sync(closeChain), + install: () => Effect.void, + openConnect: Effect.sync(openConnect), + uninstall: () => Effect.void, + }) + ), + Layer.succeed( + WidgetNavigation, + WidgetNavigation.of({ back: () => Effect.void, push, replace }) + ), + Layer.succeed( + TrackingService, + TrackingService.of({ trackEvent, trackPageView: () => Effect.void }) + ) + ) as never, + openConnect, + push, + replace, + trackEvent, + } as const; +}; + +const makeObservableRegistry = ( + ports: ReturnType +) => + AtomRegistry.make({ + initialValues: [ + [ + widgetConfigAtom, + normalizeWidgetConfig({ apiKey: "test", variant: "default" }), + ], + [appRuntime.layer, ports.layer], + ], + }); + +const readSubmitOutcome = ( + registry: AtomRegistry.AtomRegistry, + submitAtom: ReturnType["submitAtom"] +) => + expect.poll(() => + registry.get(submitAtom).pipe(AsyncResult.value, Option.getOrNull) + ); + +describe("Yield Entry", () => { + it("publishes a stable derived entry view from caller-owned input", () => { + const inputAtom = Atom.make(makeFacadeInput()); + const facade = makeYieldEntry(inputAtom, { + markSubmitted: () => undefined, + refreshKyc: () => undefined, + runAddLedgerAccount: () => Effect.void, + }); + const registry = AtomRegistry.make(); + + try { + const view = registry.get(facade.viewAtom); + expect(view.cta).toEqual({ + _tag: "Submit", + disabled: false, + loading: false, + }); + expect(view.preparation?.command.arguments?.amount).toBe("1"); + expect(view.validation.hasErrors).toBe(false); + expect(view.estimatedRewards?.rewardRateAverage.toNumber()).toBe(0.12); + } finally { + registry.dispose(); + } + }); + + it.each([ + { + expected: { _tag: "Hidden" }, + name: "no yields", + override: { hasNoYields: true }, + }, + { + expected: { _tag: "Hidden" }, + name: "external provider owns connection", + override: { connected: false, externalProviders: true }, + }, + { + expected: { + _tag: "ConnectWallet", + disabled: true, + loading: true, + }, + name: "wallet is connecting", + override: { appLoading: true, connected: false }, + }, + { + expected: { + _tag: "AddLedgerAccount", + disabled: false, + loading: false, + }, + name: "Ledger account is a placeholder", + override: { ledgerAccountPlaceholder: true }, + }, + { + expected: { _tag: "Submit", disabled: true, loading: false }, + name: "entry is invalid", + override: { canSubmit: false }, + }, + { + expected: { _tag: "Submit", disabled: true, loading: false }, + name: "KYC blocks submission", + override: { kycBlocking: true }, + }, + { + expected: { _tag: "Submit", disabled: false, loading: false }, + name: "connected entry is valid", + override: {}, + }, + ])("resolves the $name CTA branch", ({ expected, override }) => { + expect( + getYieldEntryCta({ + appLoading: false, + canSubmit: true, + connected: true, + externalProviders: false, + hasNoYields: false, + isFetching: false, + kycBlocking: false, + kycLoading: false, + ledgerAccountPlaceholder: false, + preparationAvailable: true, + ...override, + }) + ).toEqual(expected); + }); + + it("starts and navigates only for an eligible submission", async () => { + const registry = AtomRegistry.make({ + initialValues: [ + [ + widgetConfigAtom, + normalizeWidgetConfig({ apiKey: "test", variant: "default" }), + ], + ], + }); + const validInput = makeFacadeInput(); + const inputAtom = Atom.make( + makeFacadeInput({ + entry: { + ...validInput.entry, + amount: new BigNumber(0), + }, + }) + ); + const facade = makeYieldEntry(inputAtom, { + markSubmitted: (context) => { + const current = context(inputAtom); + context.set(inputAtom, { ...current, submitted: true }); + }, + refreshKyc: () => undefined, + runAddLedgerAccount: () => Effect.void, + }); + + try { + const router = registry.get(applicationRouterAtom); + registry.set(facade.submitAtom, undefined); + await expect + .poll(() => + registry + .get(facade.submitAtom) + .pipe(AsyncResult.value, Option.getOrNull) + ) + .toBe("invalid"); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom) + ).toBeNull(); + + registry.set(inputAtom, { ...validInput, isKycBlocking: true }); + registry.set(facade.submitAtom, undefined); + await expect + .poll(() => + registry + .get(facade.submitAtom) + .pipe(AsyncResult.value, Option.getOrNull) + ) + .toBe("kyc-blocked"); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom) + ).toBeNull(); + + registry.set(inputAtom, validInput); + registry.set(facade.submitAtom, undefined); + await expect + .poll(() => + registry + .get(facade.submitAtom) + .pipe(AsyncResult.value, Option.getOrNull) + ) + .toBe("submitted"); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom)?.intake._tag + ).toBe("Enter"); + await expect.poll(() => router.state.location.pathname).toBe("/review"); + } finally { + registry.dispose(); + } + }); + + it("starts one session and pushes Review through the navigation port", async () => { + const ports = makeObservablePorts(); + const input = makeFacadeInput(); + const facade = makeYieldEntry(Atom.make(input), { + markSubmitted: () => undefined, + refreshKyc: () => undefined, + runAddLedgerAccount: () => Effect.void, + }); + const registry = makeObservableRegistry(ports); + + try { + registry.set(facade.submitAtom, undefined); + await readSubmitOutcome(registry, facade.submitAtom).toBe("submitted"); + expect(ports.push).toHaveBeenCalledWith( + input.destination.reviewPath, + expect.objectContaining({ _tag: "Push" }) + ); + expect(ports.replace).not.toHaveBeenCalled(); + expect(ports.openConnect).not.toHaveBeenCalled(); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom)?.intake._tag + ).toBe("Enter"); + } finally { + registry.dispose(); + } + }); + + it("does not publish a session when Review navigation fails", async () => { + const navigationFailure = { + _tag: "WidgetNavigationError", + cause: new Error("navigation failed"), + } as never; + const registry = AtomRegistry.make({ + initialValues: [ + [ + widgetConfigAtom, + normalizeWidgetConfig({ apiKey: "test", variant: "default" }), + ], + [ + appRuntime.layer, + Layer.succeed( + WidgetNavigation, + WidgetNavigation.of({ + back: () => Effect.void, + push: () => Effect.fail(navigationFailure), + replace: () => Effect.void, + }) + ) as never, + ], + ], + }); + const facade = makeYieldEntry(Atom.make(makeFacadeInput()), { + markSubmitted: () => undefined, + refreshKyc: () => undefined, + runAddLedgerAccount: () => Effect.void, + }); + + try { + registry.set(facade.submitAtom, undefined); + await expect + .poll(() => AsyncResult.isFailure(registry.get(facade.submitAtom))) + .toBe(true); + await expect + .poll(() => registry.get(classicFlowSessionStore.currentSessionAtom)) + .toBeNull(); + } finally { + registry.dispose(); + } + }); + + it("opens the wallet modal and tracks the intent without starting a session", async () => { + const ports = makeObservablePorts(); + const inputAtom = Atom.make( + makeFacadeInput({ + connected: false, + wallet: { + additionalAddresses: null, + address: null, + isLedgerLive: false, + }, + walletScope: null, + }) + ); + const facade = makeYieldEntry(inputAtom, { + markSubmitted: () => undefined, + onConnectWallet: () => + TrackingService.use((tracking) => + tracking.trackEvent("connectWalletClicked") + ), + refreshKyc: () => undefined, + runAddLedgerAccount: () => Effect.void, + }); + const registry = makeObservableRegistry(ports); + + try { + registry.set(facade.submitAtom, undefined); + await readSubmitOutcome(registry, facade.submitAtom).toBe( + "connecting-wallet" + ); + expect(ports.openConnect).toHaveBeenCalledOnce(); + expect(ports.trackEvent).toHaveBeenCalledWith("connectWalletClicked"); + expect(ports.push).not.toHaveBeenCalled(); + expect(ports.replace).not.toHaveBeenCalled(); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom) + ).toBeNull(); + } finally { + registry.dispose(); + } + }); + + it("routes Ledger placeholders to account setup only", async () => { + const ports = makeObservablePorts(); + const addLedgerAccount = vi.fn(() => + TrackingService.use((tracking) => + tracking.trackEvent("addLedgerAccountClicked") + ) + ); + const inputAtom = Atom.make( + makeFacadeInput({ isLedgerAccountPlaceholder: true }) + ); + const facade = makeYieldEntry(inputAtom, { + markSubmitted: () => undefined, + refreshKyc: () => undefined, + runAddLedgerAccount: addLedgerAccount, + }); + const registry = makeObservableRegistry(ports); + + try { + registry.set(facade.submitAtom, undefined); + await readSubmitOutcome(registry, facade.submitAtom).toBe( + "ledger-account" + ); + expect(addLedgerAccount).toHaveBeenCalledOnce(); + expect(ports.trackEvent).toHaveBeenCalledWith("addLedgerAccountClicked"); + expect(ports.openConnect).not.toHaveBeenCalled(); + expect(ports.push).not.toHaveBeenCalled(); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom) + ).toBeNull(); + } finally { + registry.dispose(); + } + }); + + it.each([ + { + expected: "stale-owner", + name: "stale owner", + override: { isOwnerCurrent: false }, + }, + { + expected: "invalid", + name: "entry failing validation", + override: { + entry: { + ...makeFacadeInput().entry, + amount: new BigNumber(0), + }, + }, + }, + { + expected: "kyc-blocked", + name: "KYC-blocked entry", + override: { isKycBlocking: true }, + }, + { + expected: "unavailable", + name: "missing preparation", + override: { + entry: { + ...makeFacadeInput().entry, + token: null, + }, + }, + }, + { + expected: "unavailable", + name: "external-provider-owned connection", + override: { + connected: false, + externalProviders: true, + wallet: { + additionalAddresses: null, + address: null, + isLedgerLive: false, + }, + walletScope: null, + }, + }, + { + expected: "unavailable", + name: "wallet connection already in progress", + override: { + connected: false, + isWalletConnecting: true, + wallet: { + additionalAddresses: null, + address: null, + isLedgerLive: false, + }, + walletScope: null, + }, + }, + ] as const)("rejects a $name before starting a session", async ({ + expected, + override, + }) => { + const ports = makeObservablePorts(); + const inputAtom = Atom.make(makeFacadeInput(override)); + const connect = vi.fn(() => Effect.void); + const addLedgerAccount = vi.fn(() => Effect.void); + const facade = makeYieldEntry(inputAtom, { + markSubmitted: () => undefined, + onConnectWallet: connect, + refreshKyc: () => undefined, + runAddLedgerAccount: addLedgerAccount, + }); + const registry = makeObservableRegistry(ports); + + try { + registry.set(facade.submitAtom, undefined); + await readSubmitOutcome(registry, facade.submitAtom).toBe(expected); + expect( + registry.get(classicFlowSessionStore.currentSessionAtom) + ).toBeNull(); + expect(connect).not.toHaveBeenCalled(); + expect(addLedgerAccount).not.toHaveBeenCalled(); + expect(ports.openConnect).not.toHaveBeenCalled(); + expect(ports.closeChain).not.toHaveBeenCalled(); + expect(ports.trackEvent).not.toHaveBeenCalled(); + expect(ports.push).not.toHaveBeenCalled(); + expect(ports.replace).not.toHaveBeenCalled(); + } finally { + registry.dispose(); + } + }); +}); diff --git a/packages/widget/tests/features/yield-summary.test.ts b/packages/widget/tests/features/yield-summary.test.ts new file mode 100644 index 000000000..622b9a895 --- /dev/null +++ b/packages/widget/tests/features/yield-summary.test.ts @@ -0,0 +1,107 @@ +import { Cause, Option } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { makeYieldSummary } from "../../src/features/yield-summary"; +import { yieldApiYieldFixture } from "../fixtures"; + +describe("Yield Summary", () => { + it("publishes semantic provider, reward-token, and yield-type facts", () => { + const selectedYield = yieldApiYieldFixture(); + const inputAtom = Atom.make({ + selectedProviderYieldId: null, + validators: new Map(), + yield: selectedYield, + }); + const summary = makeYieldSummary(inputAtom, { + providerYieldsResultAtom: Atom.make(AsyncResult.success([])), + }); + const registry = AtomRegistry.make(); + + try { + expect(registry.get(summary.viewAtom)).toMatchObject({ + error: null, + providers: [ + { + name: selectedYield.metadata.name, + }, + ], + rewardToken: null, + status: "ready", + }); + expect(registry.get(summary.viewAtom).yieldType).not.toBeNull(); + } finally { + registry.dispose(); + } + }); + + it("normalizes an unavailable provider-yield resource", () => { + const inputAtom = Atom.make({ + selectedProviderYieldId: null, + validators: new Map(), + yield: yieldApiYieldFixture(), + }); + const failure = new Error("provider yields unavailable"); + const summary = makeYieldSummary(inputAtom, { + providerYieldsResultAtom: Atom.make(AsyncResult.fail(failure)), + }); + const registry = AtomRegistry.make(); + + try { + expect(registry.get(summary.viewAtom)).toMatchObject({ + error: { + _tag: "YieldSummaryResourceError", + cause: failure, + retryable: true, + }, + providers: null, + status: "failed", + }); + } finally { + registry.dispose(); + } + }); + + it("retains usable projections while refreshing and after refresh failure", () => { + const selectedYield = yieldApiYieldFixture(); + const inputAtom = Atom.make({ + selectedProviderYieldId: null, + validators: new Map(), + yield: selectedYield, + }); + const previous = AsyncResult.success([]); + const providerYieldsResultAtom = + Atom.make< + AsyncResult.AsyncResult, Error> + >(previous); + const summary = makeYieldSummary(inputAtom, { + providerYieldsResultAtom, + }); + const registry = AtomRegistry.make(); + + try { + registry.set(providerYieldsResultAtom, AsyncResult.waiting(previous)); + expect(registry.get(summary.viewAtom)).toMatchObject({ + error: null, + providers: [{ name: selectedYield.metadata.name }], + status: "refreshing", + }); + + const failure = new Error("refresh failed"); + registry.set( + providerYieldsResultAtom, + AsyncResult.failure(Cause.fail(failure), { + previousSuccess: Option.some(previous), + }) + ); + expect(registry.get(summary.viewAtom)).toMatchObject({ + error: { cause: failure }, + providers: [{ name: selectedYield.metadata.name }], + status: "ready", + }); + } finally { + registry.dispose(); + } + }); +}); diff --git a/packages/widget/tests/fixtures/index.ts b/packages/widget/tests/fixtures/index.ts index 5eac6de20..112bb94bf 100644 --- a/packages/widget/tests/fixtures/index.ts +++ b/packages/widget/tests/fixtures/index.ts @@ -1,23 +1,28 @@ import { faker } from "@faker-js/faker"; +import { DateTime, Schema } from "effect"; +import { + ActionTransaction, + YieldAction, +} from "../../src/domain/schema/action-models"; import type { - YieldActionDto, - YieldTransactionDto, -} from "../../src/domain/types/action"; + EarnBalance, + EarnProvider, + EarnValidator, +} from "../../src/domain/schema/earn-models"; +import { + EarnProvider as EarnProviderSchema, + EarnYield, +} from "../../src/domain/schema/earn-models"; import { EvmNetworks } from "../../src/domain/types/chains/networks"; -import type { YieldBalanceDto } from "../../src/domain/types/positions"; import type { YieldRewardRateDto } from "../../src/domain/types/reward-rate"; -import type { Yield } from "../../src/domain/types/yields"; import type { TokenDto as LegacyTokenDto, YieldDto as LegacyYieldDto, -} from "../../src/generated/api/legacy"; -import type { - ValidatorDto, - ProviderDto as YieldApiProviderDto, -} from "../../src/generated/api/yield"; - -type YieldApiYieldDto = Omit; +} from "../generated/legacy-api-types"; +import type { YieldDto as YieldApiYieldDto } from "../generated/yield-api-types"; +type ValidatorDto = typeof EarnValidator.Encoded; +type YieldApiProviderDto = typeof EarnProvider.Encoded; const apyFaker = () => faker.number.float({ min: 0, max: 0.05 }); export const yieldRewardRateFixture = ( @@ -43,16 +48,17 @@ const yieldApiTokenFixture = ( export const yieldApiProviderFixture = ( overrides?: Partial -): YieldApiProviderDto => ({ - id: "stakekit", - name: "StakeKit", - description: "", - logoURI: "https://assets.stakek.it/providers/stakekit.svg", - website: "https://stakek.it", - tvlUsd: null, - type: "protocol", - ...overrides, -}); +): EarnProvider => + Schema.decodeUnknownSync(EarnProviderSchema)({ + id: "stakekit", + name: "StakeKit", + description: "", + logoURI: "https://assets.stakek.it/app/composition/providers/stakekit.svg", + website: "https://stakek.it", + tvlUsd: null, + type: "protocol", + ...overrides, + }); type YieldRiskSummaryDto = NonNullable; type YieldRiskEntryDto = YieldRiskSummaryDto["ratings"][number]; @@ -72,7 +78,7 @@ export const yieldRiskSummaryFixture = ( ...overrides, }); -export const yieldApiYieldFixture = ( +export const yieldApiYieldDtoFixture = ( overrides?: Partial ): YieldApiYieldDto => { const token = overrides?.token ?? yieldApiTokenFixture(); @@ -85,7 +91,7 @@ export const yieldApiYieldFixture = ( outputToken: overrides?.outputToken ?? token, token, tokens, - rewardRate: yieldRewardRateFixture(overrides?.rewardRate), + rewardRate: overrides?.rewardRate ?? yieldRewardRateFixture(), status: { enter: true, exit: true }, metadata: { name: "Ethereum Staking", @@ -115,12 +121,17 @@ export const yieldApiYieldFixture = ( }, }, }, + prime: false, providerId: "stakekit", - validators: [], ...overrides, - } as YieldApiYieldDto; + }; }; +export const yieldApiYieldFixture = ( + overrides?: Partial +): typeof EarnYield.Type => + Schema.decodeUnknownSync(EarnYield)(yieldApiYieldDtoFixture(overrides)); + export const yieldApiValidatorFixture = ( overrides?: Partial ): ValidatorDto => ({ @@ -135,8 +146,8 @@ export const yieldApiValidatorFixture = ( }); export const yieldBalanceFixture = ( - overrides?: Partial -): YieldBalanceDto => { + overrides?: Partial +): typeof EarnBalance.Encoded => { const token = overrides?.token ?? yieldApiYieldFixture().token; return { @@ -148,7 +159,7 @@ export const yieldBalanceFixture = ( token, isEarning: true, ...overrides, - } as YieldBalanceDto; + } as typeof EarnBalance.Encoded; }; export const legacyYieldFixture = ( @@ -197,7 +208,8 @@ export const legacyYieldFixture = ( name: "StakeKit", description: "", externalLink: "https://stakek.it", - logoURI: "https://assets.stakek.it/providers/stakekit.svg", + logoURI: + "https://assets.stakek.it/app/composition/providers/stakekit.svg", }, revshare: { enabled: false }, rewardClaiming: "auto", @@ -224,35 +236,51 @@ export const yieldApiValidatorsFixture = ( yieldApiValidatorFixture(validator) ); +export const yieldApiTransactionDtoFixture = ( + overrides?: Partial +): typeof ActionTransaction.Encoded => ({ + id: faker.string.uuid(), + title: "Stake", + network: "ethereum", + status: "CREATED", + type: "STAKE", + hash: null, + createdAt: "2100-01-01T00:00:00.000Z", + broadcastedAt: null, + signedTransaction: null, + unsignedTransaction: null, + stepIndex: 0, + annotatedTransaction: null, + structuredTransaction: null, + explorerUrl: null, + isMessage: false, + ...overrides, +}); + +type TransactionFixtureOverrides = Partial< + Omit +> & { + readonly id?: string; +}; + export const yieldApiTransactionFixture = ( - overrides?: Partial -): YieldTransactionDto => - ({ - id: faker.string.uuid(), - title: "Stake", - network: "ethereum", - status: "CREATED", - type: "STAKE", - hash: null, - createdAt: new Date(0).toISOString(), - broadcastedAt: null, - signedTransaction: null, - unsignedTransaction: null, - stepIndex: 0, - annotatedTransaction: null, - structuredTransaction: null, - explorerUrl: null, - isMessage: false, + overrides?: TransactionFixtureOverrides +): typeof ActionTransaction.Type => { + const transaction = Schema.decodeUnknownSync(ActionTransaction)( + yieldApiTransactionDtoFixture() + ); + + return { + ...transaction, ...overrides, - }) as YieldTransactionDto; + } as typeof ActionTransaction.Type; +}; -export const yieldApiActionFixture = ( - overrides?: Partial -): YieldActionDto => { +export const yieldApiActionDtoFixture = ( + overrides?: Partial +): typeof YieldAction.Encoded => { const type = overrides?.type ?? "STAKE"; - const intent = - overrides?.intent ?? - (type === "STAKE" ? "enter" : type === "UNSTAKE" ? "exit" : "manage"); + const intent = overrides?.intent ?? getYieldActionIntent(type); return { id: faker.string.uuid(), @@ -264,13 +292,73 @@ export const yieldApiActionFixture = ( amountRaw: null, amountUsd: null, transactions: [ - yieldApiTransactionFixture({ type: type as YieldTransactionDto["type"] }), + yieldApiTransactionDtoFixture({ + type: type as (typeof ActionTransaction.Type)["type"], + }), ], executionPattern: "synchronous", rawArguments: null, - createdAt: new Date(0).toISOString(), + createdAt: "2100-01-01T00:00:00.000Z", completedAt: null, status: "CREATED", ...overrides, - } as YieldActionDto; + }; +}; + +type ActionFixtureOverrides = Partial< + Omit< + typeof YieldAction.Type, + | "address" + | "completedAt" + | "createdAt" + | "id" + | "rawArguments" + | "transactions" + | "yieldId" + > +> & { + readonly address?: string; + readonly completedAt?: DateTime.Utc | null; + readonly createdAt?: DateTime.Utc; + readonly id?: string; + readonly rawArguments?: typeof YieldAction.Encoded.rawArguments; + readonly transactions?: ReadonlyArray; + readonly yieldId?: string; +}; + +export const yieldApiActionFixture = ( + overrides?: ActionFixtureOverrides +): typeof YieldAction.Type => { + const type = overrides?.type ?? "STAKE"; + const intent = overrides?.intent ?? getYieldActionIntent(type); + const { completedAt, createdAt, transactions, ...rest } = overrides ?? {}; + + return Schema.decodeUnknownSync(YieldAction)({ + ...yieldApiActionDtoFixture(), + ...rest, + ...(completedAt === undefined + ? {} + : { + completedAt: + completedAt === null ? null : DateTime.formatIso(completedAt), + }), + ...(createdAt === undefined + ? {} + : { createdAt: DateTime.formatIso(createdAt) }), + intent, + ...(transactions + ? { + transactions: transactions.map((transaction) => + Schema.encodeSync(ActionTransaction)(transaction) + ), + } + : {}), + type, + }); +}; + +const getYieldActionIntent = (type: (typeof YieldAction.Type)["type"]) => { + if (type === "STAKE") return "enter" as const; + if (type === "UNSTAKE") return "exit" as const; + return "manage" as const; }; diff --git a/packages/widget/tests/generated/borrow-api.test.ts b/packages/widget/tests/generated/borrow-api.test.ts new file mode 100644 index 000000000..e590bcd7d --- /dev/null +++ b/packages/widget/tests/generated/borrow-api.test.ts @@ -0,0 +1,42 @@ +import { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import { HttpClient } from "effect/unstable/http"; +import { describe, expect, it } from "vitest"; +import * as BorrowApi from "../../src/generated/api/borrow"; +import * as BorrowClient from "../../src/generated/api/borrow-client"; + +describe("generated Borrow API", () => { + it("exposes runtime schemas for borrow domain DTOs", () => { + expect(Schema.isSchema(BorrowApi.IntegrationDto)).toBe(true); + expect(Schema.isSchema(BorrowApi.MarketDto)).toBe(true); + expect(Schema.isSchema(BorrowApi.PositionDto)).toBe(true); + expect(Schema.isSchema(BorrowApi.ActionDto)).toBe(true); + expect(Schema.isSchema(BorrowApi.TransactionDto)).toBe(true); + expect("make" in BorrowApi).toBe(false); + }); + + it("exposes typed Effect client operations separately from schemas", () => { + const client = BorrowClient.make( + HttpClient.make(() => Effect.die("operation must not execute")) + ); + + expect(client).toEqual( + expect.objectContaining({ + ActionsControllerExecuteActionV1: expect.any(Function), + ActionsControllerGetActionV1: expect.any(Function), + ActionsControllerGetActionsV1: expect.any(Function), + ActionsControllerStepV1: expect.any(Function), + HealthControllerHealth: expect.any(Function), + IntegrationsControllerGetIntegrationV1: expect.any(Function), + IntegrationsControllerGetIntegrationsV1: expect.any(Function), + MarketsControllerGetMarketByIdV1: expect.any(Function), + MarketsControllerGetMarketsV1: expect.any(Function), + PositionsControllerGetPositionsV1: expect.any(Function), + TransactionsControllerSubmitTransactionV1: expect.any(Function), + }) + ); + expect( + Effect.isEffect(client.IntegrationsControllerGetIntegrationsV1(undefined)) + ).toBe(true); + }); +}); diff --git a/packages/widget/tests/generated/legacy-api-types.ts b/packages/widget/tests/generated/legacy-api-types.ts new file mode 100644 index 000000000..ddc516f98 --- /dev/null +++ b/packages/widget/tests/generated/legacy-api-types.ts @@ -0,0 +1,7 @@ +export type { + ActionDto, + AddressesDto, + TokenDto, + YieldDto, + YieldRewardsSummaryResponseDto, +} from "../../src/generated/api/legacy-schema"; diff --git a/packages/widget/tests/generated/legacy-yield-api.test.ts b/packages/widget/tests/generated/legacy-yield-api.test.ts new file mode 100644 index 000000000..f62502406 --- /dev/null +++ b/packages/widget/tests/generated/legacy-yield-api.test.ts @@ -0,0 +1,180 @@ +import { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import { HttpClient } from "effect/unstable/http"; +import { describe, expect, it } from "vitest"; +import * as LegacyClient from "../../src/generated/api/legacy"; +import * as LegacySchema from "../../src/generated/api/legacy-schema"; +import * as YieldClient from "../../src/generated/api/yield"; +import * as YieldSchema from "../../src/generated/api/yield-schema"; +import { yieldApiYieldDtoFixture, yieldBalanceFixture } from "../fixtures"; + +const httpClient = HttpClient.make(() => + Effect.die("generated operation must not execute") +); + +describe("generated Legacy API", () => { + it("exposes runtime DTO schemas", () => { + expect(Schema.isSchema(LegacySchema.HealthStatusDto)).toBe(true); + expect(Schema.isSchema(LegacySchema.PriceResponseDto)).toBe(true); + expect(Schema.isSchema(LegacySchema.TokenDto)).toBe(true); + expect(Schema.isSchema(LegacySchema.YieldDto)).toBe(true); + expect("make" in LegacySchema).toBe(false); + }); + + it("exposes typed Effect client operations separately from schemas", () => { + const client = LegacyClient.make(httpClient); + + expect(client).toEqual( + expect.objectContaining({ + TokenControllerGetTokenPrices: expect.any(Function), + YieldControllerGetMyNetworks: expect.any(Function), + }) + ); + expect( + Effect.isEffect(client.YieldControllerGetMyNetworks(undefined)) + ).toBe(true); + }); +}); + +describe("generated Yield API", () => { + it("exposes runtime DTO schemas", () => { + expect(Schema.isSchema(YieldSchema.ActionDto)).toBe(true); + expect(Schema.isSchema(YieldSchema.HealthStatusDto)).toBe(true); + expect(Schema.isSchema(YieldSchema.TokenDto)).toBe(true); + expect(Schema.isSchema(YieldSchema.ValidatorDto)).toBe(true); + expect(Schema.isSchema(YieldSchema.YieldDto)).toBe(true); + expect("make" in YieldSchema).toBe(false); + }); + + it("exposes typed Effect client operations separately from schemas", () => { + const client = YieldClient.make(httpClient); + + expect(client).toEqual( + expect.objectContaining({ + YieldsControllerGetYield: expect.any(Function), + YieldsControllerGetYields: expect.any(Function), + }) + ); + expect(Effect.isEffect(client.YieldsControllerGetYields(undefined))).toBe( + true + ); + }); + + it("decodes concrete nullable DTO fields without empty-object placeholders", () => { + expect( + Schema.decodeUnknownSync(YieldSchema.YieldFeeConfigurationDto)({ + id: "66f299cd-aaaa-4bbb-8ccc-d1f26e3a02db", + default: true, + managementFeeBps: 100, + performanceFeeBps: 1000, + depositFeeBps: 0, + allocatorVaultContractAddress: + "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b", + }) + ).toEqual({ + id: "66f299cd-aaaa-4bbb-8ccc-d1f26e3a02db", + default: true, + managementFeeBps: 100, + performanceFeeBps: 1000, + depositFeeBps: 0, + allocatorVaultContractAddress: + "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b", + }); + + expect( + Schema.decodeUnknownSync(YieldSchema.ProviderDto)({ + name: "StakeKit", + id: "stakekit", + logoURI: "https://stakek.it/logo.svg", + description: "Infrastructure provider", + website: "https://stakek.it", + tvlUsd: "10200000", + type: "protocol", + }).tvlUsd + ).toBe("10200000"); + + expect( + Schema.decodeUnknownSync(YieldSchema.ValidatorDto)({ + address: "validator-address", + provider: { + name: "StakeKit", + id: "stakekit", + logoURI: "https://stakek.it/logo.svg", + description: "Infrastructure provider", + website: "https://stakek.it", + tvlUsd: "10200000", + type: "validator_provider", + rank: 1, + preferred: true, + }, + }).provider?.tvlUsd + ).toBe("10200000"); + + expect( + Schema.decodeUnknownSync(YieldSchema.YieldDto)({ + ...yieldApiYieldDtoFixture(), + curator: { + name: "Curator", + description: "Curated vault", + logoURI: "https://stakek.it/curator.svg", + }, + }).curator + ).toEqual({ + name: "Curator", + description: "Curated vault", + logoURI: "https://stakek.it/curator.svg", + }); + }); + + it("decodes concrete risk metrics and validates balance price ranges", () => { + expect( + Schema.decodeUnknownSync(YieldSchema.YieldRiskCredoraDto)({ + rating: "A", + score: 4.5, + psl: 0.01, + publishDate: "2026-01-01", + curator: "Credora", + }) + ).toEqual({ + rating: "A", + score: 4.5, + psl: 0.01, + publishDate: "2026-01-01", + curator: "Credora", + }); + + expect( + Schema.decodeUnknownSync(YieldSchema.YieldRiskStakingRewardsDto)({ + rating: "A", + score: 4, + potentialRating: "A+", + potentialScore: 4.5, + ratedAt: "2026-01-01", + ratedSince: "2025-01-01", + profileUrl: "https://stakingrewards.com/provider", + reportUrl: "https://stakingrewards.com/report", + providerName: "Provider", + version: "v1", + type: "validator", + chain: "ethereum", + contractAddress: "0x0000000000000000000000000000000000000001", + riskMetrics: { users: 1000 }, + }).riskMetrics + ).toEqual({ users: 1000 }); + + expect( + Schema.decodeUnknownSync(YieldSchema.BalanceDto)( + yieldBalanceFixture({ + priceRange: { min: "2700", max: "3310" }, + }) + ).priceRange + ).toEqual({ min: "2700", max: "3310" }); + expect(() => + Schema.decodeUnknownSync(YieldSchema.BalanceDto)( + yieldBalanceFixture({ + priceRange: { min: 2700, max: 3310 } as never, + }) + ) + ).toThrow(); + }); +}); diff --git a/packages/widget/tests/generated/yield-api-types.ts b/packages/widget/tests/generated/yield-api-types.ts new file mode 100644 index 000000000..7376d2ebb --- /dev/null +++ b/packages/widget/tests/generated/yield-api-types.ts @@ -0,0 +1 @@ +export type { YieldDto } from "../../src/generated/api/yield-schema"; diff --git a/packages/widget/tests/hooks/action-preview.dom.test.tsx b/packages/widget/tests/hooks/action-preview.dom.test.tsx new file mode 100644 index 000000000..7eefbf0a4 --- /dev/null +++ b/packages/widget/tests/hooks/action-preview.dom.test.tsx @@ -0,0 +1,284 @@ +import { RegistryProvider, useAtomSet, useAtomValue } from "@effect/atom-react"; +import { Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import { HttpResponse, http } from "msw"; +import { type PropsWithChildren, useEffect } from "react"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { ActionCommand } from "../../src/domain/schema/action-models"; +import { + type ClassicFlowSession, + classicFlowSessionStore, +} from "../../src/features/classic-transaction-flow/facade"; +import type { ClassicTransactionFlowIntake } from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { makeClassicTransactionFlowDestination } from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { + makeClassicFlowExecutionScope, + makeClassicFlowReviewScope, + makeClassicFlowSessionModule, +} from "../../src/features/classic-transaction-flow/state/classic-flow-session-facade"; +import { currentWalletStateResultAtom } from "../../src/features/wallet/state/root-atom"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { + yieldApiActionFixture, + yieldApiTransactionFixture, + yieldApiYieldFixture, +} from "../fixtures"; +import { TestAtomRuntimeProvider } from "../utils/atom-runtime-provider"; +import { describe, expect, it } from "../utils/test-extend.dom"; +import { renderHook } from "../utils/test-utils.dom"; + +const yieldApiUrl = "https://yield.example.com"; +const command = Schema.decodeUnknownSync(ActionCommand)({ + address: "0xWallet", + yieldId: "ethereum-eth-native-staking", +}); +const stake = yieldApiYieldFixture({ id: command.yieldId }); + +const Wrapper = ({ children }: PropsWithChildren) => ( + + {children} + +); + +const settings = normalizeWidgetConfig({ + apiKey: "test-key", + baseUrl: "https://api.example.com", + variant: "default", + yieldsApiUrl: yieldApiUrl, +}); + +const sessionRootAtomFamily = Atom.family((session: ClassicFlowSession) => + makeClassicFlowSessionModule(session) +); + +const reviewScopeAtomFamily = Atom.family((session: ClassicFlowSession) => + (() => { + const rootAtom = sessionRootAtomFamily(session); + let reviewAtom: ReturnType | undefined; + + return Atom.make((get) => { + const flow = get(rootAtom); + reviewAtom ??= makeClassicFlowReviewScope(flow); + return get(reviewAtom); + }); + })() +); +const sessionReviewFacadeAtom = Atom.make((get) => { + const session = get(classicFlowSessionStore.currentSessionAtom); + return session ? get(reviewScopeAtomFamily(session)) : null; +}); +const sessionReviewViewAtom = Atom.make((get) => { + const review = get(sessionReviewFacadeAtom); + return review ? get(review.reviewViewAtom) : null; +}); +const sessionKycGateAtom = Atom.make((get) => { + return get(sessionReviewViewAtom)?.kyc ?? null; +}); +const refreshSessionKycAtom = Atom.fnSync( + (_input: undefined, get) => { + const review = get(sessionReviewFacadeAtom); + if (review) get.set(review.refreshKycAtom, undefined); + }, + { initialValue: undefined } +); +const confirmSessionAtom = Atom.fnSync( + (_input: undefined, get) => { + const review = get(sessionReviewFacadeAtom); + if (review) get.set(review.confirmAtom, undefined); + }, + { initialValue: undefined } +); +const sessionAttachedActionAtom = Atom.make((get) => { + const session = get(classicFlowSessionStore.currentSessionAtom); + if (!session) return null; + + const flow = get(sessionRootAtomFamily(session)); + const execution = get(makeClassicFlowExecutionScope(flow)); + return execution ? get(execution.actionAtom) : null; +}); + +const ConnectedWrapper = ({ children }: PropsWithChildren) => ( + + {children} + +); + +describe("action preview", () => { + it("exposes a strictly decoded action from the Effect API service", async ({ + worker, + }) => { + const transaction = yieldApiTransactionFixture({ + gasEstimate: JSON.stringify({ + amount: "0.01", + token: { + decimals: 18, + name: "Ethereum", + network: "ethereum", + symbol: "ETH", + }, + }), + id: "transaction-1", + network: "ethereum", + }); + worker.use( + http.post(`${yieldApiUrl}/v1/actions/enter`, () => + HttpResponse.json( + yieldApiActionFixture({ + address: "0xWallet", + id: "action-1", + transactions: [transaction], + yieldId: "ethereum-eth-native-staking", + }) + ) + ) + ); + + const { result } = await renderHook( + () => { + const startFlow = useAtomSet(classicFlowSessionStore.startAtom); + + useEffect(() => { + startFlow({ + destination: makeClassicTransactionFlowDestination({ + routeBase: "", + }), + intake: { + _tag: "Enter", + gasFeeToken: stake.mechanics.gasFeeToken, + providersDetails: [], + request: command, + selectedStake: stake, + selectedToken: stake.token, + selectedValidators: new Map(), + walletScope: new WalletScopeKey({ + address: command.address, + network: "ethereum", + }), + } satisfies ClassicTransactionFlowIntake, + }); + }, [startFlow]); + + return useAtomValue(sessionReviewViewAtom); + }, + { wrapper: Wrapper } + ); + + const getAction = () => result.current?.action; + await expect.poll(() => getAction()?.id).toBe("action-1"); + expect(getAction()?.transactions[0]?.gasEstimate).toBe( + transaction.gasEstimate + ); + }); + + it("does not preview a KYC-blocked Enter flow", async ({ worker }) => { + let actionPreviewCalls = 0; + let kycStatusCalls = 0; + let kycStatus = "not_started"; + const selectedStake = yieldApiYieldFixture(); + const kycRequiredStake = { + ...selectedStake, + mechanics: { + ...selectedStake.mechanics, + requirements: { + ...selectedStake.mechanics.requirements, + kycRequired: true, + }, + }, + }; + + worker.use( + http.get(`${yieldApiUrl}/v1/yields/:yieldId/kyc/status`, () => { + kycStatusCalls += 1; + return HttpResponse.json({ kycStatus }); + }), + http.post(`${yieldApiUrl}/v1/actions/enter`, () => { + actionPreviewCalls += 1; + return HttpResponse.json(yieldApiActionFixture()); + }) + ); + + const { act, result } = await renderHook( + () => { + const startFlow = useAtomSet(classicFlowSessionStore.startAtom); + + useEffect(() => { + startFlow({ + destination: makeClassicTransactionFlowDestination({ + routeBase: "", + }), + intake: { + _tag: "Enter", + gasFeeToken: kycRequiredStake.mechanics.gasFeeToken, + providersDetails: [], + request: command, + selectedStake: kycRequiredStake, + selectedToken: kycRequiredStake.token, + selectedValidators: new Map(), + walletScope: new WalletScopeKey({ + address: command.address, + network: "ethereum", + }), + }, + }); + }, [startFlow]); + + return { + kyc: useAtomValue(sessionKycGateAtom), + review: useAtomValue(sessionReviewViewAtom), + refreshKyc: useAtomSet(refreshSessionKycAtom), + confirmFlow: useAtomSet(confirmSessionAtom), + attachedAction: useAtomValue(sessionAttachedActionAtom), + }; + }, + { wrapper: ConnectedWrapper } + ); + + await act(async () => { + await expect.poll(() => kycStatusCalls).toBe(1); + }); + expect(actionPreviewCalls).toBe(0); + expect(result.current.kyc?.isGateBlocking).toBe(true); + expect(result.current.review?.action).toBeNull(); + await act(async () => result.current.confirmFlow(undefined)); + expect(result.current.attachedAction).toBeNull(); + + kycStatus = "approved"; + await act(async () => { + result.current.refreshKyc(undefined); + await expect.poll(() => kycStatusCalls).toBe(2); + await expect.poll(() => actionPreviewCalls).toBe(1); + }); + await expect.poll(() => result.current.review?.action).not.toBeNull(); + expect(result.current.kyc?.isGateBlocking).toBe(false); + }); +}); diff --git a/packages/widget/tests/hooks/activity-actions.test.ts b/packages/widget/tests/hooks/activity-actions.test.ts index 2ca9caed1..d436ff988 100644 --- a/packages/widget/tests/hooks/activity-actions.test.ts +++ b/packages/widget/tests/hooks/activity-actions.test.ts @@ -1,178 +1,333 @@ +import { Effect, Layer, Option, Schema } from "effect"; +import { Atom, AtomRegistry } from "effect/unstable/reactivity"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { describe, expect, it, vi } from "vitest"; -import type { ActionsControllerGetActionsParams } from "../../src/generated/api/yield"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ActivityActionsPage } from "../../src/domain/schema/activity-models"; +import { activityActionsPullAtom } from "../../src/features/activity/resources/activity-actions"; +import { ActivityActionsKey } from "../../src/features/activity/resources/activity-requests"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { getPullResultItems } from "../../src/shared/effect/pagination"; import { - fetchActivityFilterOptions, - getActivityActionsQueryKey, - getActivityActionsRequestParams, -} from "../../src/hooks/api/use-activity-actions"; -import type { ActivityFilter } from "../../src/pages/details/activity-page/activity-filters"; -import type { ApiClient } from "../../src/providers/api/api-client"; - -const address = "0x0000000000000000000000000000000000000001"; -const network = "ethereum"; - -const sort = (values: ReadonlyArray) => [...values].sort(); - -const yieldTypeKey = (values?: ReadonlyArray) => - values ? sort(values).join("|") : ""; - -const filterByYieldTypes = new Map([ - ["", "all"], - [yieldTypeKey(["staking", "restaking", "liquid_staking"]), "stake"], - [ - yieldTypeKey([ - "lending", - "vault", - "fixed_yield", - "concentrated_liquidity_pool", - "liquidity_pool", - ]), - "defi", - ], - [yieldTypeKey(["real_world_asset"]), "rwa"], -]); - -const getFilterFromParams = ( - params: ActionsControllerGetActionsParams -): ActivityFilter => { - const filter = filterByYieldTypes.get(yieldTypeKey(params.yieldTypes)); - - if (!filter) { - throw new Error(`Unexpected yieldTypes: ${params.yieldTypes?.join(",")}`); - } - - return filter; -}; - -const createApiClient = (totals: Partial>) => { - const calls: ActionsControllerGetActionsParams[] = []; - const ActionsControllerGetActions = vi.fn( - async ({ params }: { params: ActionsControllerGetActionsParams }) => { - calls.push(params); - - return { - items: [], - limit: params.limit ?? 1, - offset: params.offset ?? 0, - total: totals[getFilterFromParams(params)] ?? 0, - }; - } - ); - const apiClient = { - withOptions: vi.fn(() => ({ - yield: { - ActionsControllerGetActions, - }, - })), - } as unknown as ApiClient; - - return { apiClient, calls }; -}; - -describe("activity action request params", () => { - it("omits yieldTypes for all activity", () => { - const params = getActivityActionsRequestParams({ - address, - filter: "all", - limit: 50, - network, - offset: 0, - }); + yieldApiActionDtoFixture, + yieldApiActionFixture, + yieldApiValidatorFixture, + yieldApiYieldFixture, +} from "../fixtures"; - expect(params).not.toHaveProperty("yieldTypes"); - expect(params).toMatchObject({ - address, - limit: 50, - network, - offset: 0, - statuses: ["SUCCESS", "FAILED"], - }); +const address = Schema.decodeUnknownSync( + Schema.NonEmptyString.pipe(Schema.brand("WalletAddress")) +)("0x0000000000000000000000000000000000000001"); +const network = "ethereum" as const; +const walletScope = new WalletScopeKey({ address, network }); +const getActivityActions = ( + result: Atom.Type> +) => getPullResultItems(result).flatMap((batch) => batch.actions); + +describe("activity action atom boundary", () => { + it("constructs value-equal activity resource keys", () => { + const fields = { + filter: "stake" as const, + scope: new WalletScopeKey({ address, network }), + }; + + expect(new ActivityActionsKey(fields)).toEqual( + new ActivityActionsKey({ ...fields }) + ); }); - it.each([ - ["stake", ["staking", "restaking", "liquid_staking"]], - [ - "defi", - [ - "lending", - "vault", - "fixed_yield", - "concentrated_liquidity_pool", - "liquidity_pool", + it("omits a malformed action while retaining valid siblings", async () => { + const valid = yieldApiActionDtoFixture({ id: "valid-action" }); + const malformed: Record = { + ...yieldApiActionDtoFixture({ id: "invalid-action" }), + transactions: [ + { + ...yieldApiActionDtoFixture().transactions[0]!, + network: "not-a-network", + }, ], - ], - ["rwa", ["real_world_asset"]], - ] as const satisfies ReadonlyArray< - readonly [ActivityFilter, ReadonlyArray] - >)("adds %s yieldTypes to activity requests", (filter, yieldTypes) => { - const params = getActivityActionsRequestParams({ - address, - filter, - limit: 50, - network, - offset: 0, - }); + }; + const page = await Effect.runPromise( + Schema.decodeUnknownEffect(ActivityActionsPage)({ + items: [valid, malformed], + limit: 50, + offset: 0, + total: 2, + }) + ); - expect(sort(params.yieldTypes ?? [])).toEqual(sort(yieldTypes)); + expect(page.items?.map((item) => item.id)).toEqual(["valid-action"]); + expect(page.total).toBe(2); }); - it("uses different query keys for each selected filter", () => { - const allKey = getActivityActionsQueryKey({ - address, - filter: "all", - network, - }); - const stakeKey = getActivityActionsQueryKey({ - address, - filter: "stake", - network, + it("loads activity presentation one semantic page at a time", async () => { + const yieldModel = yieldApiYieldFixture(); + const getOpportunity = vi.fn(() => Effect.succeed(yieldModel)); + const actions = Array.from({ length: 51 }, (_, index) => + yieldApiActionFixture({ + id: `action-${index}`, + yieldId: yieldModel.id, + }) + ); + const listActivity = vi.fn( + ({ + limit, + offset, + }: { + readonly limit: number; + readonly offset: number; + }) => + Effect.succeed({ + items: actions.slice(offset, offset + limit), + limit, + offset, + total: actions.length, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ + getOpportunity, + getProvider: () => Effect.succeed(Option.none()), + listActivity, + } as never) + ) + ), + ], }); + const resource = activityActionsPullAtom( + new ActivityActionsKey({ filter: "all", scope: walletScope }) + ); + const unmount = registry.mount(resource); + + await vi.waitFor(() => + expect(getActivityActions(registry.get(resource))).toHaveLength(50) + ); + expect(AsyncResult.getOrThrow(registry.get(resource)).done).toBe(false); + expect(listActivity).toHaveBeenCalledOnce(); + + registry.set(resource, undefined); - expect(allKey).not.toEqual(stakeKey); - expect(stakeKey[1]).toMatchObject({ - filter: "stake", - yieldTypes: ["staking", "restaking", "liquid_staking"], + await vi.waitFor(() => + expect(getActivityActions(registry.get(resource))).toHaveLength(51) + ); + expect(AsyncResult.getOrThrow(registry.get(resource)).done).toBe(true); + expect(listActivity).toHaveBeenCalledTimes(2); + expect(getOpportunity).toHaveBeenCalledOnce(); + expect( + getActivityActions(registry.get(resource)).every( + (action) => action.yieldData?.id === yieldModel.id + ) + ).toBe(true); + + unmount(); + registry.dispose(); + }); + + it("bounds and deduplicates concurrent enrichment requests", async () => { + let inFlight = 0; + let maxInFlight = 0; + const tracked =
(value: A) => + Effect.acquireUseRelease( + Effect.sync(() => { + inFlight += 1; + maxInFlight = Math.max(maxInFlight, inFlight); + }), + () => Effect.sleep("20 millis").pipe(Effect.as(value)), + () => + Effect.sync(() => { + inFlight -= 1; + }) + ); + const yields = Array.from({ length: 8 }, (_, index) => + yieldApiYieldFixture({ + id: `activity-yield-${index}`, + providerId: `activity-provider-${index}`, + }) + ); + const actions = yields.flatMap((yieldModel, index) => + [0, 1].map((duplicate) => + yieldApiActionFixture({ + id: `action-${index}-${duplicate}`, + rawArguments: { validatorAddress: `validator-${index}` }, + yieldId: yieldModel.id, + }) + ) + ); + const yieldsById = new Map( + yields.map((yieldModel) => [yieldModel.id, yieldModel]) + ); + const getOpportunity = vi.fn((yieldId: (typeof yields)[number]["id"]) => + tracked(yieldsById.get(yieldId)!) + ); + const getProvider = vi.fn(() => tracked(Option.none())); + const listValidators = vi.fn( + (request: { + readonly address?: string; + readonly limit: number; + readonly offset: number; + }) => + tracked({ + items: [yieldApiValidatorFixture({ address: request.address })], + limit: request.limit, + offset: request.offset, + total: 1, + }) + ); + const listActivity = vi.fn(() => + Effect.succeed({ + items: actions, + limit: 50, + offset: 0, + total: actions.length, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ + getOpportunity, + getProvider, + listActivity, + listValidators, + } as never) + ) + ), + ], }); + const resource = activityActionsPullAtom( + new ActivityActionsKey({ filter: "all", scope: walletScope }) + ); + const unmount = registry.mount(resource); + + await vi.waitFor(() => + expect(getActivityActions(registry.get(resource))).toHaveLength( + actions.length + ) + ); + + expect(maxInFlight).toBeGreaterThan(1); + expect(maxInFlight).toBeLessThanOrEqual(5); + expect(getOpportunity).toHaveBeenCalledTimes(yields.length); + expect(getProvider).toHaveBeenCalledTimes(yields.length); + expect(listValidators).toHaveBeenCalledTimes(yields.length); + + unmount(); + registry.dispose(); }); -}); -describe("fetchActivityFilterOptions", () => { - it("returns exact all and non-zero category counts without borrow", async () => { - const { apiClient, calls } = createApiClient({ - all: 7, - stake: 2, - defi: 0, - rwa: 3, + it("keeps activity usable when per-yield enrichment fails", async () => { + const yieldModel = yieldApiYieldFixture(); + const listActivity = vi.fn(() => + Effect.succeed({ + items: [ + yieldApiActionFixture({ id: "action-a", yieldId: yieldModel.id }), + ], + limit: 50, + offset: 0, + total: 1, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ + getOpportunity: () => Effect.fail(new Error("unavailable")), + listActivity, + } as never) + ) + ), + ], }); + const resource = activityActionsPullAtom( + new ActivityActionsKey({ filter: "all", scope: walletScope }) + ); + const unmount = registry.mount(resource); + + await vi.waitFor(() => + expect(getActivityActions(registry.get(resource))).toMatchObject([ + { actionData: { id: "action-a" }, yieldData: null }, + ]) + ); + + unmount(); + registry.dispose(); + }); - const options = await fetchActivityFilterOptions({ - address, - apiClient, - network, + it("manual refresh republishes data from the shared history authority", async () => { + const yieldModel = yieldApiYieldFixture(); + let actionId = "action-before-refresh"; + const listActivity = vi.fn( + ({ + limit, + offset, + }: { + readonly limit: number; + readonly offset: number; + }) => + Effect.succeed({ + items: [ + yieldApiActionFixture({ id: actionId, yieldId: yieldModel.id }), + ], + limit, + offset, + total: 1, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ + getProvider: () => Effect.succeed(Option.none()), + listActivity, + listYields: (request: { readonly limit: number }) => + Effect.succeed({ + items: [yieldModel], + limit: request.limit, + offset: 0, + total: 1, + }), + } as never) + ) + ), + ], }); + const resource = activityActionsPullAtom( + new ActivityActionsKey({ filter: "all", scope: walletScope }) + ); + const unmount = registry.mount(resource); - expect(options).toEqual([ - { filter: "all", count: 7 }, - { filter: "stake", count: 2 }, - { filter: "rwa", count: 3 }, - ]); - expect(options.map((option) => option.filter)).not.toContain("borrow"); - - expect(calls.map(getFilterFromParams)).toEqual([ - "all", - "stake", - "defi", - "rwa", - ]); - expect( - calls.every( - (params) => - params.address === address && - params.network === network && - params.limit === 1 && - params.offset === 0 && - params.statuses?.join("|") === "SUCCESS|FAILED" + await vi.waitFor(() => + expect(getActivityActions(registry.get(resource))[0]?.actionData.id).toBe( + "action-before-refresh" ) - ).toBe(true); + ); + + actionId = "action-after-refresh"; + registry.refresh(resource); + + await vi.waitFor(() => + expect(getActivityActions(registry.get(resource))[0]?.actionData.id).toBe( + "action-after-refresh" + ) + ); + expect(listActivity).toHaveBeenCalledTimes(2); + + unmount(); + registry.dispose(); }); }); diff --git a/packages/widget/tests/hooks/default-tokens.test.ts b/packages/widget/tests/hooks/default-tokens.test.ts deleted file mode 100644 index 91c7fbddf..000000000 --- a/packages/widget/tests/hooks/default-tokens.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { QueryClient } from "@tanstack/react-query"; -import { describe, expect, it, vi } from "vitest"; -import type { TokenBalanceScanResponseDto } from "../../src/domain/types/token-balance"; -import type { TokenDto } from "../../src/domain/types/tokens"; -import type { TokenWithAvailableYieldsDto as LegacyTokenWithAvailableYieldsDto } from "../../src/generated/api/legacy"; -import type { TokenWithAvailableYieldsDto as YieldTokenWithAvailableYieldsDto } from "../../src/generated/api/yield"; -import { - fetchDefaultTokens, - getDefaultTokens, -} from "../../src/hooks/api/use-default-tokens"; -import type { ApiClient } from "../../src/providers/api/api-client"; - -const createToken = (symbol: string): TokenDto => ({ - name: symbol, - symbol, - decimals: 18, - network: "ethereum", - logoURI: `https://assets.stakek.it/tokens/${symbol.toLowerCase()}.svg`, -}); - -const createTokenWithYields = ( - symbol: string, - yieldId = `${symbol.toLowerCase()}-yield` -): YieldTokenWithAvailableYieldsDto => ({ - token: createToken(symbol) as YieldTokenWithAvailableYieldsDto["token"], - availableYields: [yieldId], -}); - -const createApiClient = ({ - getLegacyTokens = vi.fn(), - getYieldTokens = vi.fn(), -}: { - getLegacyTokens?: ReturnType; - getYieldTokens?: ReturnType; -}) => - ({ - withOptions: () => ({ - legacy: { - TokenControllerGetTokens: getLegacyTokens, - }, - yield: { - TokensControllerGetTokens: getYieldTokens, - }, - }), - }) as unknown as ApiClient; - -describe("fetchDefaultTokens", () => { - it("uses the yield API with network and yield type filters", async () => { - const tokens = [ - createTokenWithYields("ETH", "eth-staking"), - createTokenWithYields("SOL", "sol-staking"), - createTokenWithYields("USDC", "usdc-staking"), - ]; - const getLegacyTokens = vi.fn(); - const getYieldTokens = vi.fn(async ({ params }) => ({ - items: tokens.slice(params.offset, params.offset + params.limit), - total: tokens.length, - offset: params.offset, - limit: params.limit, - })); - const apiClient = createApiClient({ getLegacyTokens, getYieldTokens }); - - const { pages } = await fetchDefaultTokens({ - apiClient, - limit: 2, - network: "ethereum", - offset: 0, - yieldTypes: ["staking"], - }); - - expect(pages).toHaveLength(2); - expect(pages[0]).toMatchObject({ - limit: 2, - tokens: tokens.slice(0, 2).map( - (tokenWithYields): TokenBalanceScanResponseDto => ({ - ...tokenWithYields, - amount: "0", - }) - ), - nextOffset: 2, - offset: 0, - total: 3, - }); - expect(pages[1].tokens).toEqual( - [tokens[2]].map((tokenWithYields) => ({ - ...tokenWithYields, - amount: "0", - })) - ); - expect(getLegacyTokens).not.toHaveBeenCalled(); - expect(getYieldTokens.mock.calls.map(([arg]) => arg.params.offset)).toEqual( - [0, 2] - ); - }); - - it("uses the legacy API when no enabled-yield filter is requested", async () => { - const token = { - token: createToken("ETH") as LegacyTokenWithAvailableYieldsDto["token"], - availableYields: ["eth-staking"], - }; - const getLegacyTokens = vi.fn(async () => [token]); - const getYieldTokens = vi.fn(); - const apiClient = createApiClient({ getLegacyTokens, getYieldTokens }); - - const { pages } = await fetchDefaultTokens({ - apiClient, - network: "ethereum", - }); - - expect(pages).toHaveLength(1); - expect(pages[0].tokens).toEqual([{ ...token, amount: "0" }]); - expect(pages[0].nextOffset).toBeUndefined(); - expect(getYieldTokens).not.toHaveBeenCalled(); - expect(getLegacyTokens).toHaveBeenCalledWith({ - params: { - enabledYieldsOnly: undefined, - network: "ethereum", - }, - }); - }); -}); - -describe("getDefaultTokens", () => { - it("fetches every page from the paginated yield API", async () => { - const tokens = [ - createTokenWithYields("ETH"), - createTokenWithYields("SOL"), - createTokenWithYields("USDC"), - createTokenWithYields("ATOM"), - createTokenWithYields("OSMO"), - ]; - let activePageRequests = 0; - let maxActivePageRequests = 0; - const getYieldTokens = vi.fn( - async ({ params }: { params: { offset?: number; limit?: number } }) => { - const offset = params.offset ?? 0; - const limit = params.limit ?? 2; - - if (offset > 0) { - activePageRequests += 1; - maxActivePageRequests = Math.max( - maxActivePageRequests, - activePageRequests - ); - await new Promise((resolve) => setTimeout(resolve, 0)); - activePageRequests -= 1; - } - - return { - items: tokens.slice(offset, offset + limit), - total: tokens.length, - offset, - limit, - }; - } - ); - const apiClient = createApiClient({ getYieldTokens }); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - const result = await getDefaultTokens({ - apiClient, - enabledYieldsOnly: true, - limit: 2, - network: "ethereum", - queryClient, - }).run(); - - expect(result.unsafeCoerce().map((item) => item.token.symbol)).toEqual([ - "ETH", - "SOL", - "USDC", - "ATOM", - "OSMO", - ]); - expect(getYieldTokens.mock.calls.map(([arg]) => arg.params.offset)).toEqual( - [0, 2, 4] - ); - expect(maxActivePageRequests).toBe(2); - }); -}); diff --git a/packages/widget/tests/hooks/summary-atoms.test.ts b/packages/widget/tests/hooks/summary-atoms.test.ts new file mode 100644 index 000000000..bffd80976 --- /dev/null +++ b/packages/widget/tests/hooks/summary-atoms.test.ts @@ -0,0 +1,191 @@ +import { Effect, Layer, Option, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { EarnPosition } from "../../src/domain/schema/earn-models"; +import { toPositionsData } from "../../src/domain/types/positions"; +import { getDashboardYieldCategory } from "../../src/domain/types/yields"; +import { + currentGroupedPositionsAtom, + positionsTableDataAtom, + toPositionItems, +} from "../../src/features/portfolio/resources/positions"; +import { + getPositionsAverageApy, + getPositionsTotal, +} from "../../src/features/portfolio/resources/summary"; +import { + MultiYieldsKey, + multiYieldsByIdAtom, +} from "../../src/features/yield-summary/multi-yields"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { + yieldApiProviderFixture, + yieldApiYieldFixture, + yieldBalanceFixture, +} from "../fixtures"; + +const makePosition = ({ + amountUsd, + rewardRate, + yieldId, +}: { + readonly amountUsd: string; + readonly rewardRate: number; + readonly yieldId: string; +}) => { + const yieldDto = yieldApiYieldFixture({ + id: yieldId, + rewardRate: { components: [], rateType: "APY", total: rewardRate }, + }); + const position = Schema.decodeUnknownSync(EarnPosition)({ + balances: [ + yieldBalanceFixture({ + amount: "1", + amountUsd, + token: yieldDto.token, + }), + ], + outputTokenBalance: null, + yieldId: yieldDto.id, + }); + const item = toPositionItems(toPositionsData([position]), false)[0]!; + + return { item, yieldDto }; +}; + +describe("summary atom derivations", () => { + it("recomputes position total and weighted APY when source state changes", () => { + const first = makePosition({ + amountUsd: "5", + rewardRate: 0.05, + yieldId: "yield-1", + }); + const second = makePosition({ + amountUsd: "15", + rewardRate: 0.1, + yieldId: "yield-2", + }); + const yields = new Map([ + [first.yieldDto.id, first.yieldDto], + [second.yieldDto.id, second.yieldDto], + ]); + const positionsAtom = Atom.make([first.item]); + const totalAtom = Atom.make((get) => + getPositionsTotal(get(positionsAtom), yields) + ); + const averageApyAtom = Atom.make((get) => + getPositionsAverageApy(get(positionsAtom), yields) + ); + const registry = AtomRegistry.make(); + + expect(registry.get(totalAtom).toFixed()).toBe("5"); + expect(registry.get(averageApyAtom).toFixed()).toBe("5"); + + registry.set(positionsAtom, [first.item, second.item]); + + expect(registry.get(totalAtom).toFixed()).toBe("20"); + expect(registry.get(averageApyAtom).toFixed()).toBe("8.75"); + }); + + it("retains a non-enterable Yield referenced by a historical position", () => { + const historicalYield = yieldApiYieldFixture({ + status: { enter: false, exit: true }, + }); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, { + getProvider: () => + Effect.succeed(Option.some(yieldApiProviderFixture())), + listYields: () => + Effect.succeed({ + items: [historicalYield], + limit: 100, + offset: 0, + total: 1, + }), + } as never) + ), + ], + }); + + const key = new MultiYieldsKey({ yieldIds: [historicalYield.id] }); + const result = registry.get(multiYieldsByIdAtom(key)); + const yields = AsyncResult.getOrThrow(result); + const historicalPosition = makePosition({ + amountUsd: "5", + rewardRate: historicalYield.rewardRate.total, + yieldId: historicalYield.id, + }); + + expect(yields.has(historicalYield.id)).toBe(true); + expect(getPositionsTotal([historicalPosition.item], yields).toFixed()).toBe( + "5" + ); + }); + + it("publishes category-grouped position rows from the Portfolio atom", async () => { + const position = makePosition({ + amountUsd: "5", + rewardRate: 0.05, + yieldId: "yield-1", + }); + const category = getDashboardYieldCategory(position.yieldDto); + if (!category) throw new Error("expected a dashboard Yield category"); + const listYields = vi.fn(() => + Effect.succeed({ + items: [position.yieldDto], + limit: 100, + offset: 0, + total: 1, + }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, { + listYields, + } as never) + ), + [ + widgetConfigAtom, + normalizeWidgetConfig({ + apiKey: "test-key", + baseUrl: "https://api.example.com", + dashboardVariant: true, + variant: "default", + yieldGrouping: "category", + yieldsApiUrl: "https://yield.example.com", + }), + ], + [positionsTableDataAtom, AsyncResult.success([position.item])], + ], + }); + const resource = currentGroupedPositionsAtom; + const unmount = registry.mount(resource); + + await vi.waitFor(() => + expect(registry.get(resource)).toEqual([ + { kind: "chain-modal" }, + { category, count: 1, kind: "section" }, + { + item: { kind: "earn", position: position.item }, + kind: "position", + }, + ]) + ); + expect(listYields).toHaveBeenCalledOnce(); + + unmount(); + registry.dispose(); + }); +}); diff --git a/packages/widget/tests/hooks/use-debounced-value.test.tsx b/packages/widget/tests/hooks/use-debounced-value.test.tsx deleted file mode 100644 index 8077fd5e3..000000000 --- a/packages/widget/tests/hooks/use-debounced-value.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useDebouncedValue } from "../../src/hooks/use-debounced-value"; -import { describe, expect, it, vi } from "../utils/test-extend"; -import { renderHook } from "../utils/test-utils"; - -describe("useDebouncedValue", () => { - it("updates only after the debounce delay", async () => { - vi.useFakeTimers(); - - try { - const hook = await renderHook( - (props) => useDebouncedValue(props?.value ?? "", 300), - { initialProps: { value: "" } } - ); - - expect(hook.result.current).toBe(""); - - await hook.rerender({ value: "ta" }); - - expect(hook.result.current).toBe(""); - - await hook.act(() => vi.advanceTimersByTime(299)); - - expect(hook.result.current).toBe(""); - - await hook.act(() => vi.advanceTimersByTime(1)); - - expect(hook.result.current).toBe("ta"); - } finally { - vi.useRealTimers(); - } - }); -}); diff --git a/packages/widget/tests/hooks/validator-loading.dom.test.tsx b/packages/widget/tests/hooks/validator-loading.dom.test.tsx new file mode 100644 index 000000000..c538c92be --- /dev/null +++ b/packages/widget/tests/hooks/validator-loading.dom.test.tsx @@ -0,0 +1,106 @@ +import { useAtom } from "@effect/atom-react"; +import { Array as EArray, Option, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { delay, HttpResponse, http } from "msw"; +import type { PropsWithChildren } from "react"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { YieldId } from "../../src/domain/schema/identifiers"; +import { + YieldValidatorsKey, + yieldValidatorsPullAtom, +} from "../../src/features/yield-entry/yield-validators"; +import { getPullResultItems } from "../../src/shared/effect/pagination"; +import { yieldApiValidatorFixture } from "../fixtures"; +import { TestAtomRuntimeProvider } from "../utils/atom-runtime-provider"; +import { describe, expect, it } from "../utils/test-extend.dom"; +import { renderHook } from "../utils/test-utils.dom"; + +const yieldApiUrl = "https://yield.example.com"; + +const Wrapper = ({ children }: PropsWithChildren) => ( + + {children} + +); + +describe("validator loading", () => { + it("loads one page per Pull, exposes waiting state, and omits malformed entries", async ({ + worker, + }) => { + let requestCount = 0; + worker.use( + http.get( + `${yieldApiUrl}/v1/yields/:yieldId/validators`, + async ({ request }) => { + requestCount += 1; + const offset = Number( + new URL(request.url).searchParams.get("offset") + ); + if (offset > 0) await delay(50); + const valid = yieldApiValidatorFixture({ + address: `validator-${offset}`, + }); + + return HttpResponse.json({ + items: + offset === 0 ? [valid, { ...valid, address: null }] : [valid], + total: 101, + offset, + limit: 100, + }); + } + ) + ); + + const hook = await renderHook( + () => { + const [result, pull] = useAtom( + yieldValidatorsPullAtom( + new YieldValidatorsKey({ + network: "ethereum", + search: null, + yieldId: Schema.decodeSync(YieldId)("yield-1"), + }) + ) + ); + return { + pull, + result, + }; + }, + { wrapper: Wrapper } + ); + const validators = () => + EArray.flatMap( + getPullResultItems(hook.result.current.result), + (page) => page.items + ); + const hasNextPage = () => + hook.result.current.result.pipe( + AsyncResult.value, + Option.exists(({ done }) => !done) + ); + + await hook.act(async () => { + await expect.poll(() => validators().length).toBe(1); + }); + expect(hasNextPage()).toBe(true); + expect(requestCount).toBe(1); + + await hook.act(async () => { + hook.result.current.pull(); + await expect.poll(() => hook.result.current.result.waiting).toBe(true); + await expect.poll(() => validators().length).toBe(2); + }); + expect(requestCount).toBe(2); + expect(hasNextPage()).toBe(false); + expect(requestCount).toBe(2); + }); +}); diff --git a/packages/widget/tests/hooks/validator-loading.test.tsx b/packages/widget/tests/hooks/validator-loading.test.tsx deleted file mode 100644 index c61c53d2b..000000000 --- a/packages/widget/tests/hooks/validator-loading.test.tsx +++ /dev/null @@ -1,599 +0,0 @@ -import { delay, HttpResponse, http } from "msw"; -import type { PropsWithChildren } from "react"; -import { - getYieldValidatorQueryKey, - useYieldValidators, -} from "../../src/hooks/api/use-yield-validators"; -import { SKApiClientProvider } from "../../src/providers/api/api-client-provider"; -import { - SKQueryClientProvider, - useSKQueryClient, -} from "../../src/providers/query-client"; -import { SettingsContextProvider } from "../../src/providers/settings"; -import type { SettingsContextType } from "../../src/providers/settings/types"; -import { - yieldApiValidatorFixture, - yieldApiValidatorsFixture, -} from "../fixtures"; -import { describe, expect, it } from "../utils/test-extend"; -import { renderHook } from "../utils/test-utils"; - -const yieldApiUrl = "https://yield.example.com"; - -const createWrapper = - (validatorsConfig?: SettingsContextType["validatorsConfig"]) => - ({ children }: PropsWithChildren) => ( - - - {children} - - - ); - -const Wrapper = createWrapper(); - -const ConfiguredWrapper = createWrapper({ - ethereum: { - allowed: ["preferred-0", "allowed-200"], - }, -}); - -describe("validator loading", () => { - it("loads preferred validators first and defers non-preferred pages", async ({ - worker, - }) => { - const calls: Array<{ - limit: string | null; - offset: string | null; - preferred: string | null; - }> = []; - const preferredValidators = yieldApiValidatorsFixture( - Array.from({ length: 150 }, (_, index) => ({ - address: `preferred-${index}`, - preferred: true, - })) - ); - const otherValidators = yieldApiValidatorsFixture([ - { address: "other-0", preferred: false }, - ]); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const offset = Number(url.searchParams.get("offset") ?? 0); - const preferred = url.searchParams.get("preferred"); - - calls.push({ - limit: url.searchParams.get("limit"), - offset: url.searchParams.get("offset"), - preferred, - }); - - if (preferred === "false") { - return HttpResponse.json({ - items: otherValidators, - total: 10000, - offset, - limit: 100, - }); - } - - return HttpResponse.json({ - items: preferredValidators.slice(offset, offset + 100), - total: preferredValidators.length, - offset, - limit: 100, - }); - } - ) - ); - - const { result } = await renderHook( - () => useYieldValidators({ yieldId: "yield-1", network: "ethereum" }), - { wrapper: Wrapper } - ); - - await expect.poll(() => result.current.data?.length).toBe(150); - expect(result.current.hasNextPage).toBe(true); - - expect(calls).toContainEqual({ - limit: "100", - offset: "0", - preferred: "true", - }); - expect(calls).toContainEqual({ - limit: "1", - offset: "0", - preferred: "false", - }); - expect(calls).toContainEqual({ - limit: "100", - offset: "100", - preferred: "true", - }); - expect(calls).toHaveLength(3); - - await result.current.fetchNextPage(); - - await expect.poll(() => result.current.data?.length).toBe(151); - - expect(calls).toContainEqual({ - limit: "100", - offset: "0", - preferred: "false", - }); - }); - - it("hydrates the individual validator cache from loaded pages", async ({ - worker, - }) => { - const preferredValidator = yieldApiValidatorFixture({ - address: "preferred-0", - preferred: true, - }); - const otherValidator = yieldApiValidatorFixture({ - address: "other-0", - preferred: false, - }); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const preferred = url.searchParams.get("preferred"); - - if (preferred === "false") { - return HttpResponse.json({ - items: [otherValidator], - total: 1, - offset: 0, - limit: Number(url.searchParams.get("limit") ?? 100), - }); - } - - return HttpResponse.json({ - items: [preferredValidator], - total: 1, - offset: 0, - limit: 100, - }); - } - ) - ); - - const { result } = await renderHook( - () => ({ - queryClient: useSKQueryClient(), - validators: useYieldValidators({ - yieldId: "yield-1", - network: "ethereum", - }), - }), - { wrapper: Wrapper } - ); - - await expect.poll(() => result.current.validators.data?.length).toBe(1); - - expect( - result.current.queryClient.getQueryData( - getYieldValidatorQueryKey({ - yieldId: "yield-1", - address: preferredValidator.address, - }) - ) - ).toEqual(preferredValidator); - expect( - result.current.queryClient.getQueryData( - getYieldValidatorQueryKey({ - yieldId: "yield-1", - address: otherValidator.address, - }) - ) - ).toEqual(otherValidator); - }); - - it("skips raw pages that are empty after validator config filtering", async ({ - worker, - }) => { - const calls: Array<{ - limit: string | null; - offset: string | null; - preferred: string | null; - }> = []; - const preferredValidators = yieldApiValidatorsFixture([ - { address: "preferred-0", preferred: true }, - ]); - const blockedValidators = (offset: number) => - yieldApiValidatorsFixture( - Array.from({ length: 100 }, (_, index) => ({ - address: `blocked-${offset + index}`, - preferred: false, - })) - ); - const allowedValidators = yieldApiValidatorsFixture([ - { address: "allowed-200", preferred: false }, - ]); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const offset = Number(url.searchParams.get("offset") ?? 0); - const preferred = url.searchParams.get("preferred"); - - calls.push({ - limit: url.searchParams.get("limit"), - offset: url.searchParams.get("offset"), - preferred, - }); - - if (preferred === "true") { - return HttpResponse.json({ - items: preferredValidators, - total: preferredValidators.length, - offset, - limit: 100, - }); - } - - return HttpResponse.json({ - items: - offset === 200 ? allowedValidators : blockedValidators(offset), - total: 201, - offset, - limit: Number(url.searchParams.get("limit") ?? 100), - }); - } - ) - ); - - const { result } = await renderHook( - () => useYieldValidators({ yieldId: "yield-1", network: "ethereum" }), - { wrapper: ConfiguredWrapper } - ); - - await expect.poll(() => result.current.data?.length).toBe(1); - expect(result.current.hasNextPage).toBe(true); - - await result.current.fetchNextPage(); - - await expect.poll(() => result.current.data?.length).toBe(2); - expect(result.current.hasNextPage).toBe(false); - expect(result.current.data?.map((validator) => validator.address)).toEqual([ - "preferred-0", - "allowed-200", - ]); - expect(calls).toContainEqual({ - limit: "100", - offset: "100", - preferred: "false", - }); - expect(calls).toContainEqual({ - limit: "100", - offset: "200", - preferred: "false", - }); - }); - - it("does not expose a next page when there are no non-preferred validators", async ({ - worker, - }) => { - const calls: Array<{ - limit: string | null; - preferred: string | null; - }> = []; - const preferredValidators = yieldApiValidatorsFixture([ - { address: "preferred-0", preferred: true }, - ]); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const preferred = url.searchParams.get("preferred"); - - calls.push({ - limit: url.searchParams.get("limit"), - preferred, - }); - - if (preferred === "false") { - return HttpResponse.json({ - items: [], - total: 0, - offset: 0, - limit: Number(url.searchParams.get("limit") ?? 100), - }); - } - - return HttpResponse.json({ - items: preferredValidators, - total: preferredValidators.length, - offset: 0, - limit: 100, - }); - } - ) - ); - - const { result } = await renderHook( - () => useYieldValidators({ yieldId: "yield-1", network: "ethereum" }), - { wrapper: Wrapper } - ); - - await expect.poll(() => result.current.data?.length).toBe(1); - - expect(result.current.hasNextPage).toBe(false); - expect(calls).toContainEqual({ limit: "1", preferred: "false" }); - }); - - it("searches validators on the server by name and address", async ({ - worker, - }) => { - const calls: Array<{ name: string | null; address: string | null }> = []; - const searchedValidator = yieldApiValidatorFixture({ - address: "searched-address", - name: "Searched Validator", - }); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const name = url.searchParams.get("name"); - const address = url.searchParams.get("address"); - - calls.push({ name, address }); - - return HttpResponse.json({ - items: name || address ? [searchedValidator] : [], - total: name || address ? 1 : 0, - offset: Number(url.searchParams.get("offset") ?? 0), - limit: 100, - }); - } - ) - ); - - const { result } = await renderHook( - () => - useYieldValidators({ - yieldId: "yield-1", - network: "ethereum", - search: "searched", - }), - { wrapper: Wrapper } - ); - - await expect.poll(() => result.current.data?.length).toBe(1); - - expect(calls).toContainEqual({ name: "searched", address: null }); - expect(calls).toContainEqual({ name: null, address: "searched" }); - }); - - it("uses server totals to paginate deduplicated search results", async ({ - worker, - }) => { - const calls: Array<{ - offset: string | null; - name: string | null; - address: string | null; - }> = []; - const firstValidator = yieldApiValidatorFixture({ - address: "searched-address-0", - name: "Searched Validator 0", - }); - const secondValidator = yieldApiValidatorFixture({ - address: "searched-address-100", - name: "Searched Validator 100", - }); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const offset = Number(url.searchParams.get("offset") ?? 0); - const name = url.searchParams.get("name"); - const address = url.searchParams.get("address"); - - calls.push({ - offset: url.searchParams.get("offset"), - name, - address, - }); - - if (name === "searched") { - return HttpResponse.json({ - items: offset === 100 ? [secondValidator] : [firstValidator], - total: 101, - offset, - limit: 100, - }); - } - - return HttpResponse.json({ - items: [], - total: 0, - offset, - limit: 100, - }); - } - ) - ); - - const { result } = await renderHook( - () => - useYieldValidators({ - yieldId: "yield-1", - network: "ethereum", - search: "searched", - }), - { wrapper: Wrapper } - ); - - await expect.poll(() => result.current.data?.length).toBe(1); - expect(result.current.hasNextPage).toBe(true); - - await result.current.fetchNextPage(); - - await expect.poll(() => result.current.data?.length).toBe(2); - expect(result.current.hasNextPage).toBe(false); - expect(calls).toContainEqual({ - offset: "100", - name: "searched", - address: null, - }); - expect(calls).toContainEqual({ - offset: "100", - name: null, - address: "searched", - }); - }); - - it("does not add independent search totals when checking for more pages", async ({ - worker, - }) => { - const nameValidator = yieldApiValidatorFixture({ - address: "searched-name-address", - name: "Searched Name Validator", - }); - const addressValidator = yieldApiValidatorFixture({ - address: "searched-address-address", - name: "Searched Address Validator", - }); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - ({ request }) => { - const url = new URL(request.url); - const name = url.searchParams.get("name"); - const address = url.searchParams.get("address"); - - return HttpResponse.json({ - items: name ? [nameValidator] : address ? [addressValidator] : [], - total: name || address ? 80 : 0, - offset: Number(url.searchParams.get("offset") ?? 0), - limit: 100, - }); - } - ) - ); - - const { result } = await renderHook( - () => - useYieldValidators({ - yieldId: "yield-1", - network: "ethereum", - search: "searched", - }), - { wrapper: Wrapper } - ); - - await expect.poll(() => result.current.data?.length).toBe(2); - - expect(result.current.hasNextPage).toBe(false); - }); - - it("keeps previous validators while a new search is loading", async ({ - worker, - }) => { - const defaultValidator = yieldApiValidatorFixture({ - address: "default-address", - name: "Default Validator", - preferred: true, - }); - const searchedValidator = yieldApiValidatorFixture({ - address: "searched-address", - name: "Searched Validator", - }); - - worker.use( - http.get( - `${yieldApiUrl}/v1/yields/:yieldId/validators`, - async ({ request }) => { - const url = new URL(request.url); - const name = url.searchParams.get("name"); - const address = url.searchParams.get("address"); - const preferred = url.searchParams.get("preferred"); - - await delay(50); - - if (name === "searched") { - return HttpResponse.json({ - items: [searchedValidator], - total: 1, - offset: 0, - limit: 100, - }); - } - - if (address === "searched") { - return HttpResponse.json({ - items: [], - total: 0, - offset: 0, - limit: 100, - }); - } - - if (preferred === "false") { - return HttpResponse.json({ - items: [], - total: 0, - offset: 0, - limit: Number(url.searchParams.get("limit") ?? 100), - }); - } - - return HttpResponse.json({ - items: [defaultValidator], - total: 1, - offset: 0, - limit: 100, - }); - } - ) - ); - - const { result, rerender } = await renderHook( - (props) => - useYieldValidators({ - yieldId: "yield-1", - network: "ethereum", - search: props?.search, - }), - { - initialProps: { search: "" }, - wrapper: Wrapper, - } - ); - - await expect - .poll(() => result.current.data?.map((validator) => validator.address)) - .toEqual(["default-address"]); - - await rerender({ search: "searched" }); - - expect(result.current.data?.map((validator) => validator.address)).toEqual([ - "default-address", - ]); - - await expect - .poll(() => result.current.data?.map((validator) => validator.address)) - .toEqual(["searched-address"]); - }); -}); diff --git a/packages/widget/tests/mocks/api-routes.ts b/packages/widget/tests/mocks/api-routes.ts index e9b7b3c20..8757b5ae2 100644 --- a/packages/widget/tests/mocks/api-routes.ts +++ b/packages/widget/tests/mocks/api-routes.ts @@ -1,4 +1,4 @@ -import { config } from "../../src/config"; +import { config } from "../../src/shared/config/widget-defaults"; const getApiRoute = (baseUrl: string, path: string) => new URL(path.startsWith("/") ? path : `/${path}`, baseUrl).toString(); @@ -8,3 +8,6 @@ export const legacyApiRoute = (path: string) => export const yieldApiRoute = (path: string) => getApiRoute(config.env.yieldsApiUrl, path); + +export const borrowApiRoute = (path: string) => + getApiRoute(config.env.borrowApiUrl, path); diff --git a/packages/widget/tests/mocks/legacy-api-handlers.ts b/packages/widget/tests/mocks/legacy-api-handlers.ts index 81a238916..215468b13 100644 --- a/packages/widget/tests/mocks/legacy-api-handlers.ts +++ b/packages/widget/tests/mocks/legacy-api-handlers.ts @@ -1,9 +1,9 @@ import { HttpResponse, http } from "msw"; +import { legacyYieldFixture } from "../fixtures"; import type { TokenDto, YieldRewardsSummaryResponseDto, -} from "../../src/generated/api/legacy"; -import { legacyYieldFixture } from "../fixtures"; +} from "../generated/legacy-api-types"; import { legacyApiRoute } from "./api-routes"; import { mockDelay } from "./delay"; diff --git a/packages/widget/tests/mocks/server.ts b/packages/widget/tests/mocks/server.ts new file mode 100644 index 000000000..7b37f2acf --- /dev/null +++ b/packages/widget/tests/mocks/server.ts @@ -0,0 +1,4 @@ +import { setupServer } from "msw/node"; +import { handlers } from "./handlers"; + +export const server = setupServer(...handlers); diff --git a/packages/widget/tests/mocks/yield-api-handlers.ts b/packages/widget/tests/mocks/yield-api-handlers.ts index 8b16dd912..424cbf116 100644 --- a/packages/widget/tests/mocks/yield-api-handlers.ts +++ b/packages/widget/tests/mocks/yield-api-handlers.ts @@ -1,22 +1,24 @@ +import { DateTime } from "effect"; import { HttpResponse, http } from "msw"; import type { - YieldCreateActionDto, - YieldCreateManageActionDto, -} from "../../src/domain/types/action"; -import type { TokenDto } from "../../src/domain/types/tokens"; + ActionCommand, + ManageActionCommand, +} from "../../src/domain/schema/action-models"; +import type { AppToken } from "../../src/domain/schema/legacy-models"; + import { - yieldApiActionFixture, + yieldApiActionDtoFixture, yieldApiProviderFixture, - yieldApiTransactionFixture, + yieldApiTransactionDtoFixture, yieldApiValidatorsFixture, - yieldApiYieldFixture, + yieldApiYieldDtoFixture, yieldBalanceFixture, yieldRiskSummaryFixture, } from "../fixtures"; import { yieldApiRoute } from "./api-routes"; import { mockDelay } from "./delay"; -const defaultToken: TokenDto = { +const defaultToken: AppToken = { name: "Ethereum", symbol: "ETH", decimals: 18, @@ -25,7 +27,7 @@ const defaultToken: TokenDto = { logoURI: "https://assets.stakek.it/tokens/eth.svg", }; -const defaultYield = yieldApiYieldFixture({ +const defaultYield = yieldApiYieldDtoFixture({ id: "ethereum-eth-native-staking", token: defaultToken, tokens: [defaultToken], @@ -35,17 +37,17 @@ const defaultYield = yieldApiYieldFixture({ }); const createDefaultAction = ( - body: YieldCreateActionDto | YieldCreateManageActionDto, + body: ActionCommand | ManageActionCommand, type: "STAKE" | "UNSTAKE" | "CLAIM_REWARDS" = "STAKE" ) => { - const transaction = yieldApiTransactionFixture({ + const transaction = yieldApiTransactionDtoFixture({ id: "default-transaction-id", network: defaultToken.network, status: "CREATED", type, }); - return yieldApiActionFixture({ + return yieldApiActionDtoFixture({ id: "default-action-id", yieldId: "yieldId" in body ? body.yieldId : defaultYield.id, type, @@ -57,13 +59,16 @@ const createDefaultAction = ( }); }; +const isoAt = (milliseconds: number) => + DateTime.formatIso(DateTime.makeUnsafe(milliseconds)); + export const getYieldApiMock = () => [ http.get(yieldApiRoute("/health"), async () => { await mockDelay(); return HttpResponse.json({ status: "OK", - timestamp: new Date(0).toISOString(), + timestamp: isoAt(0), }); }), @@ -114,7 +119,7 @@ export const getYieldApiMock = () => [ await mockDelay(); return HttpResponse.json( - yieldApiYieldFixture({ + yieldApiYieldDtoFixture({ ...defaultYield, id: String(params.yieldId), }) @@ -142,7 +147,7 @@ export const getYieldApiMock = () => [ return HttpResponse.json( yieldApiProviderFixture({ id: providerId, - logoURI: `https://assets.stakek.it/providers/${providerId}.svg`, + logoURI: `https://assets.stakek.it/app/composition/providers/${providerId}.svg`, name: providerNameById[providerId] ?? providerId, }) ); @@ -170,16 +175,16 @@ export const getYieldApiMock = () => [ offset: 0, limit: 20, interval: "day", - from: new Date(0).toISOString(), - to: new Date(2 * 24 * 60 * 60 * 1000).toISOString(), + from: isoAt(0), + to: isoAt(2 * 24 * 60 * 60 * 1000), items: [ - { timestamp: new Date(0).toISOString(), rewardRate: "0.04" }, + { timestamp: isoAt(0), rewardRate: "0.04" }, { - timestamp: new Date(24 * 60 * 60 * 1000).toISOString(), + timestamp: isoAt(24 * 60 * 60 * 1000), rewardRate: "0.045", }, { - timestamp: new Date(2 * 24 * 60 * 60 * 1000).toISOString(), + timestamp: isoAt(2 * 24 * 60 * 60 * 1000), rewardRate: "0.05", }, ], @@ -198,16 +203,16 @@ export const getYieldApiMock = () => [ offset: 0, limit: 20, interval: "day", - from: new Date(0).toISOString(), - to: new Date(2 * 24 * 60 * 60 * 1000).toISOString(), + from: isoAt(0), + to: isoAt(2 * 24 * 60 * 60 * 1000), items: [ - { timestamp: new Date(0).toISOString(), tvlUsd: "12000000" }, + { timestamp: isoAt(0), tvlUsd: "12000000" }, { - timestamp: new Date(24 * 60 * 60 * 1000).toISOString(), + timestamp: isoAt(24 * 60 * 60 * 1000), tvlUsd: "12500000", }, { - timestamp: new Date(2 * 24 * 60 * 60 * 1000).toISOString(), + timestamp: isoAt(2 * 24 * 60 * 60 * 1000), tvlUsd: "13100000", }, ], @@ -240,7 +245,7 @@ export const getYieldApiMock = () => [ await mockDelay(); return HttpResponse.json( - createDefaultAction((await request.json()) as YieldCreateActionDto) + createDefaultAction((await request.json()) as ActionCommand) ); }), @@ -248,10 +253,7 @@ export const getYieldApiMock = () => [ await mockDelay(); return HttpResponse.json( - createDefaultAction( - (await request.json()) as YieldCreateActionDto, - "UNSTAKE" - ) + createDefaultAction((await request.json()) as ActionCommand, "UNSTAKE") ); }), @@ -260,7 +262,7 @@ export const getYieldApiMock = () => [ return HttpResponse.json( createDefaultAction( - (await request.json()) as YieldCreateManageActionDto, + (await request.json()) as ManageActionCommand, "CLAIM_REWARDS" ) ); @@ -283,7 +285,7 @@ export const getYieldApiMock = () => [ await mockDelay(); return HttpResponse.json( - yieldApiTransactionFixture({ + yieldApiTransactionDtoFixture({ id: String(params.transactionId), }) ); @@ -296,7 +298,7 @@ export const getYieldApiMock = () => [ await mockDelay(); return HttpResponse.json( - yieldApiTransactionFixture({ + yieldApiTransactionDtoFixture({ id: String(params.transactionId), status: "BROADCASTED", }) @@ -310,7 +312,7 @@ export const getYieldApiMock = () => [ await mockDelay(); return HttpResponse.json( - yieldApiTransactionFixture({ + yieldApiTransactionDtoFixture({ id: String(params.transactionId), status: "BROADCASTED", }) diff --git a/packages/widget/tests/pages-dashboard/borrow-execution-flow.browser.test.tsx b/packages/widget/tests/pages-dashboard/borrow-execution-flow.browser.test.tsx new file mode 100644 index 000000000..54189cd82 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/borrow-execution-flow.browser.test.tsx @@ -0,0 +1,655 @@ +import { RegistryProvider, useAtomSet, useAtomValue } from "@effect/atom-react"; +import { Deferred, Effect, Layer, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import type { ReactNode } from "react"; +import { + MemoryRouter, + Route, + Routes, + useLocation, + useNavigate, +} from "react-router"; +import { base } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; +import { userEvent } from "vitest/browser"; +import type { Connector } from "wagmi"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import { Action as BorrowAction } from "../../src/domain/borrow/action"; +import { ActionRequest } from "../../src/domain/borrow/action-request"; +import type { + Transaction as BorrowTransaction, + SubmitTransactionCommand, +} from "../../src/domain/borrow/transaction"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + BorrowTransactionFlowCompletionGuard, + BorrowTransactionFlowExecutionScope, + BorrowTransactionFlowReviewRoute, + BorrowTransactionFlowRoute, + useBorrowTransactionFlow, + useBorrowTransactionFlowExecution, +} from "../../src/features/borrow-transaction-flow/react/borrow-flow-route"; +import type { BorrowTransactionFlowReview } from "../../src/features/borrow-transaction-flow/state"; +import type { BorrowFlowSession } from "../../src/features/borrow-transaction-flow/state/borrow-flow-session-store"; +import { borrowFlowSessionStore } from "../../src/features/borrow-transaction-flow/state/borrow-flow-session-store"; +import { BorrowStepsPage } from "../../src/features/borrow-transaction-flow/ui/steps"; +import { useBorrowExecution } from "../../src/features/borrow-transaction-flow/ui/use-borrow-execution"; +import { WalletScopeRoute } from "../../src/features/wallet/react/wallet-scope-route"; +import { BorrowOperations } from "../../src/services/api/borrow-operations"; +import { WidgetNavigation } from "../../src/services/navigation/widget-navigation"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { + disconnectedNormalizedWalletState, + type NormalizedWalletState, +} from "../../src/services/wallet/domain/state"; +import { WalletService } from "../../src/services/wallet/wallet-service"; +import { TransactionWorkflowOperationsService } from "../../src/services/workflow/transaction-workflow-operations-service"; +import { TransactionWorkflowService } from "../../src/services/workflow/transaction-workflow-service"; +import { render } from "../utils/test-utils"; +import type { WalletOperations } from "../utils/wallet-operations"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const transactionHash = + "0x1111111111111111111111111111111111111111111111111111111111111111"; +const walletScope = new WalletScopeKey({ address, network: "base" }); + +type ActionDto = typeof BorrowAction.Encoded; +type TransactionDto = typeof BorrowTransaction.Encoded; +type SubmitTransactionDto = typeof SubmitTransactionCommand.Encoded; + +const request = Schema.decodeUnknownSync(ActionRequest)({ + action: "borrow", + address, + args: { + amount: "25", + marketId: "morpho-blue-borrow-base-cbbtc-usdc-86", + tokenAddress: "0x0000000000000000000000000000000000000002", + }, + integrationId: "morpho-blue", +}); + +const reviewState: BorrowTransactionFlowReview = { + request, + summary: { + action: "borrow", + borrowAmount: "25", + collateralAmount: "0.5", + collateralTokenSymbol: "cbBTC", + loanTokenSymbol: "USDC", + marketLabel: "cbBTC / USDC", + network: "base", + providerName: "Morpho Blue", + }, +}; +const session: BorrowFlowSession = { + epoch: 1, + intake: { + ...reviewState, + entry: { _tag: "BorrowDashboard" }, + }, + walletScope, +}; + +const transaction = ( + overrides: Partial = {} +): TransactionDto => ({ + address, + chainId: "8453", + id: "tx-1", + network: "base", + signablePayload: JSON.stringify({ + data: "0xabcdef", + from: address, + gasLimit: "21000", + to: "0x0000000000000000000000000000000000000002", + value: "0", + }), + signingFormat: "EVM_TRANSACTION", + status: "WAITING_FOR_SIGNATURE", + type: "BORROW", + ...overrides, +}); + +const action = (overrides: Partial = {}): ActionDto => ({ + action: "borrow", + address, + createdAt: "2026-01-01T00:00:00.000Z", + currentStep: 1, + hasNextStep: false, + id: "action-1", + integrationId: "morpho-blue", + rawArguments: request.args, + status: "CREATED", + totalSteps: 1, + transactions: [transaction()], + ...overrides, +}); + +const decodedAction = (overrides: Partial = {}) => + Schema.decodeUnknownSync(BorrowAction)(action(overrides)); + +const connectedWalletState = { + additionalAddresses: null, + address, + chain: base, + connector: { id: "test", uid: "test" } as Connector, + connectorChains: [base], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "base", + status: "connected", +} satisfies NormalizedWalletState; + +const wallet = { + state: Effect.succeed({ + connection: connectedWalletState, + ledger: { + accounts: [], + currentAccountId: undefined, + disabledChains: [], + }, + }), + signTransaction: () => + Effect.succeed({ + broadcasted: true as const, + signedTx: transactionHash, + }), +} as unknown as WalletOperations; + +const makeBorrowApi = ({ + executeAction = Effect.succeed(action()), + getActions = [], + stepActions = [], +}: { + readonly executeAction?: Effect.Effect; + readonly getActions?: ReadonlyArray; + readonly stepActions?: ReadonlyArray; +}) => { + const queuedGetActions = [...getActions]; + const queuedStepActions = [...stepActions]; + + return { + executeAction: vi.fn(() => + executeAction.pipe(Effect.flatMap(Schema.decodeEffect(BorrowAction))) + ), + getAction: vi.fn(() => + Schema.decodeEffect(BorrowAction)( + queuedGetActions.shift() ?? getActions.at(-1) ?? action() + ) + ), + stepAction: vi.fn(() => + Schema.decodeEffect(BorrowAction)( + queuedStepActions.shift() ?? stepActions.at(-1) ?? action() + ) + ), + submitTransaction: vi.fn( + (_request: { + readonly command: SubmitTransactionDto; + readonly transactionId: string; + }) => + Effect.succeed({ + link: "https://basescan.org/tx/0x111", + status: "BROADCASTED" as const, + transactionHash, + }) + ), + }; +}; + +const ExecutionProbe = () => { + const execution = useBorrowExecution(); + + return ( +
+
{execution.phase}
+
{String(execution.isRunning)}
+
+ {execution.currentStep}/{execution.totalSteps} +
+
{execution.batches.length}
+
+ {execution.currentTransaction?.id ?? "none"} +
+ {execution.error && ( + + )} +
+ ); +}; + +const NavigationCapture = ({ + capture, +}: { + readonly capture: (navigate: ReturnType) => void; +}) => { + capture(useNavigate()); + return null; +}; + +const StartExecutionProbe = () => { + const flow = useBorrowTransactionFlow(); + const confirm = useAtomSet(flow.confirmAtom); + + return ( + + ); +}; + +const CompleteProbe = () => { + const location = useLocation(); + const execution = useBorrowTransactionFlowExecution(); + const result = useAtomValue(execution.viewAtom).completionResult; + + return ( +
+ {location.pathname} {result?.action.id} +
+ ); +}; + +const HistoryControls = () => { + const location = useLocation(); + const navigate = useNavigate(); + + return ( + <> +
{location.pathname}
+ + + + ); +}; + +const renderExecution = async ( + borrow: ReturnType, + options: { + readonly action?: BorrowAction; + readonly historyControls?: boolean; + readonly initialEntries?: ReadonlyArray; + readonly initialIndex?: number; + readonly initialPath?: string; + readonly stepsElement?: ReactNode; + readonly wallet?: WalletOperations; + } = {} +) => { + const navigation: { + current: ReturnType | null; + } = { current: null }; + const navigationService = WidgetNavigation.of({ + back: () => + Effect.sync(() => { + navigation.current?.(-1); + }), + push: (path, options) => + Effect.sync(() => { + navigation.current?.(path, { state: options?.state }); + }), + replace: (path, options) => + Effect.sync(() => { + navigation.current?.(path, { + replace: true, + state: options?.state, + }); + }), + }); + const activeWallet = options.wallet ?? wallet; + const workflowAction = options.action ?? decodedAction(); + borrow.executeAction.mockImplementation(() => Effect.succeed(workflowAction)); + const walletLayer = Layer.succeed( + WalletService, + activeWallet as WalletService["Service"] + ); + const operations = { + completeWorkflow: () => Effect.void, + getBorrowAction: borrow.getAction, + getClassicStatus: () => Effect.die("unexpected classic status"), + getWalletState: activeWallet.state.pipe( + Effect.map((state) => state.connection) + ), + signMessage: () => Effect.die("unexpected message signing"), + signTransaction: activeWallet.signTransaction, + stepBorrowAction: borrow.stepAction, + submitBorrowTransaction: borrow.submitTransaction, + submitClassicHash: () => Effect.die("unexpected classic hash submission"), + submitClassicSigned: () => + Effect.die("unexpected classic signed submission"), + submitWorkflow: () => Effect.void, + trackEvent: () => Effect.void, + } as unknown as TransactionWorkflowOperationsService["Service"]; + const workflowLayer = TransactionWorkflowService.layer.pipe( + Layer.provide( + Layer.succeed(TransactionWorkflowOperationsService, operations) + ) + ); + + const app = await render( + Effect.void, + trackPageView: () => Effect.void, + } as TrackingService["Service"]), + Layer.succeed(WidgetNavigation, navigationService) + ).pipe(Layer.fresh), + ], + [ + walletRuntime.layer, + Layer.mergeAll(workflowLayer, walletLayer).pipe(Layer.fresh), + ], + ]} + > + + (navigation.current = navigate)} + /> + {options.historyControls ? : null} + + + } + > + Borrow home} /> + + } + > + }> + } + /> + + }> + } + /> + }> + } /> + + + + + + + + ); + + if (options.initialPath !== "/borrow/complete") { + await userEvent.click(app.getByTestId("start-execution")); + } + + return app; +}; + +describe("borrow execution flow component", () => { + it("routes an incomplete direct completion page back to Borrow", async () => { + const app = await renderExecution(makeBorrowApi({}), { + initialPath: "/borrow/complete", + wallet: { + ...wallet, + signTransaction: () => Effect.never, + }, + }); + + await expect.element(app.getByText("Borrow home")).toBeInTheDocument(); + + app.unmount(); + }); + + it("renders running state while execution waits for wallet signing", async () => { + const app = await renderExecution(makeBorrowApi({}), { + wallet: { + ...wallet, + signTransaction: () => Effect.never, + }, + }); + + await expect.element(app.getByTestId("phase")).toHaveTextContent("signing"); + await expect.element(app.getByTestId("running")).toHaveTextContent("true"); + + app.unmount(); + }); + + it("routes to success when execution completes", async () => { + const app = await renderExecution( + makeBorrowApi({ + getActions: [ + action({ + status: "SUCCESS", + transactions: [transaction({ status: "CONFIRMED" })], + }), + ], + }) + ); + + await expect + .element(app.getByTestId("complete")) + .toHaveTextContent("/borrow/complete action-1"); + + app.unmount(); + }); + + it("renders retryable failure and routes after retry succeeds", async () => { + const app = await renderExecution( + makeBorrowApi({ + getActions: [ + action({ + status: "PROCESSING", + transactions: [transaction({ status: "FAILED" })], + }), + action({ + status: "SUCCESS", + transactions: [transaction({ status: "CONFIRMED" })], + }), + ], + }) + ); + + await expect.element(app.getByTestId("retry")).toBeInTheDocument(); + + await userEvent.click(app.getByTestId("retry")); + + await expect + .element(app.getByTestId("complete")) + .toHaveTextContent("/borrow/complete action-1"); + + app.unmount(); + }); + + it("retries after reconnecting without signing twice", async () => { + let state: NormalizedWalletState = disconnectedNormalizedWalletState; + const signTransaction = vi.fn(() => + Effect.succeed({ + broadcasted: true as const, + signedTx: transactionHash, + }) + ); + const reconnectingWallet = { + ...wallet, + state: Effect.sync(() => ({ + connection: state, + ledger: { + accounts: [], + currentAccountId: undefined, + disabledChains: [], + }, + })), + signTransaction, + }; + const app = await renderExecution( + makeBorrowApi({ + getActions: [ + action({ + status: "SUCCESS", + transactions: [transaction({ status: "CONFIRMED" })], + }), + ], + }), + { wallet: reconnectingWallet } + ); + + await expect.element(app.getByTestId("retry")).toBeInTheDocument(); + expect(signTransaction).not.toHaveBeenCalled(); + + state = connectedWalletState; + await userEvent.click(app.getByTestId("retry")); + + await expect + .element(app.getByTestId("complete")) + .toHaveTextContent("/borrow/complete action-1"); + expect(signTransaction).toHaveBeenCalledOnce(); + + app.unmount(); + }); + + it("shows the next action step while retaining prior transaction batches", async () => { + let signCalls = 0; + const multiStepWallet = { + ...wallet, + signTransaction: () => { + signCalls += 1; + return signCalls === 1 + ? Effect.succeed({ + broadcasted: true as const, + signedTx: transactionHash, + }) + : Effect.never; + }, + }; + const first = decodedAction({ + hasNextStep: true, + totalSteps: 2, + }); + const app = await renderExecution( + makeBorrowApi({ + getActions: [ + action({ + hasNextStep: true, + totalSteps: 2, + transactions: [transaction({ status: "CONFIRMED" })], + }), + ], + stepActions: [ + action({ + currentStep: 2, + id: first.id, + totalSteps: 2, + transactions: [transaction({ id: "tx-2" })], + }), + ], + }), + { action: first, wallet: multiStepWallet } + ); + + await expect + .element(app.getByTestId("action-step")) + .toHaveTextContent("2/2"); + await expect.element(app.getByTestId("batch-count")).toHaveTextContent("2"); + await expect + .element(app.getByTestId("current-transaction")) + .toHaveTextContent("tx-2"); + await expect.element(app.getByTestId("phase")).toHaveTextContent("signing"); + + app.unmount(); + }); + + it("does not restart an abandoned submitted workflow from browser history", async () => { + const confirmationInterrupted = await Effect.runPromise( + Deferred.make() + ); + const signTransaction = vi.fn(() => + Effect.succeed({ + broadcasted: true as const, + signedTx: transactionHash, + }) + ); + const borrow = makeBorrowApi({}); + borrow.getAction.mockImplementation(() => + Effect.never.pipe( + Effect.onInterrupt(() => + Deferred.succeed(confirmationInterrupted, undefined) + ) + ) + ); + const activeWallet = { ...wallet, signTransaction }; + const app = await renderExecution(borrow, { + action: decodedAction(), + historyControls: true, + initialEntries: ["/borrow", "/borrow/review"], + initialIndex: 1, + stepsElement: , + wallet: activeWallet, + }); + + await expect + .element(app.getByTestId("history-path")) + .toHaveTextContent("/borrow/steps"); + + await vi.waitFor(() => { + expect(signTransaction).toHaveBeenCalledOnce(); + expect(borrow.submitTransaction).toHaveBeenCalledOnce(); + expect(borrow.getAction).toHaveBeenCalledOnce(); + }); + + await userEvent.click(app.getByRole("button", { name: "Back" })); + await expect + .element(app.getByTestId("history-path")) + .toHaveTextContent("/borrow/review"); + await Effect.runPromise(Deferred.await(confirmationInterrupted)); + + await userEvent.click(app.getByRole("button", { name: "Back" })); + await expect + .element(app.getByTestId("history-path")) + .toHaveTextContent("/borrow"); + await userEvent.click(app.getByRole("button", { name: "Forward" })); + await expect + .element(app.getByTestId("history-path")) + .toHaveTextContent("/borrow"); + + expect( + app.container.querySelector('[data-rk="borrow-steps-page"]') + ).not.toBeInTheDocument(); + expect(signTransaction).toHaveBeenCalledOnce(); + expect(borrow.submitTransaction).toHaveBeenCalledOnce(); + + app.unmount(); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/borrow-position-action-wallet-scope.dom.test.tsx b/packages/widget/tests/pages-dashboard/borrow-position-action-wallet-scope.dom.test.tsx new file mode 100644 index 000000000..535b7d938 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/borrow-position-action-wallet-scope.dom.test.tsx @@ -0,0 +1,292 @@ +import { RegistryProvider } from "@effect/atom-react"; +import * as Schema from "effect/Schema"; +import type { TFunction } from "i18next"; +import { act } from "react"; +import { I18nextProvider } from "react-i18next"; +import { MemoryRouter, Outlet, Route, Routes } from "react-router"; +import { describe, expect, it } from "vitest"; +import { Integration } from "../../src/domain/borrow/integration"; +import { Market } from "../../src/domain/borrow/market"; +import { BorrowAccountPosition } from "../../src/domain/borrow/position"; +import { deriveBorrowPositionItems } from "../../src/domain/borrow/position-items"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { BorrowPositionActionPage } from "../../src/features/borrow/ui/position-details"; +import { + type BorrowPositionAction, + getBorrowPositionActions, +} from "../../src/features/borrow/ui/position-details-model"; +import { RootElementProvider } from "../../src/shared/react/root-element"; +import { i18nInstance } from "../../src/translation"; +import { render } from "../utils/test-utils.dom"; + +const marketDto = { + availableLiquidity: "500000", + availableLiquidityRaw: "500000000000", + borrowRate: "0.06", + collateralTokens: [ + { + liquidationPenalty: "0.05", + liquidationThreshold: "0.85", + maxLtv: "0.8", + priceUsd: "2000", + supplyRate: "0.02", + token: { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + decimals: 18, + name: "Wrapped Ether", + symbol: "WETH", + }, + }, + ], + feeWrapperAddress: null, + id: "aave-v3-ethereum-usdc", + integrationId: "aave-borrow", + isBorrowEnabled: true, + loanToken: { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + decimals: 6, + name: "USD Coin", + symbol: "USDC", + }, + loanTokenPriceUsd: "1", + minLoan: null, + network: "ethereum", + poolAddress: "0x0000000000000000000000000000000000000001", + supplyCollateralFeeBps: "0", + totalBorrow: "500000", + totalBorrowRaw: "500000000000", + totalSupply: "1000000", + totalSupplyRaw: "1000000000000", + type: "pool", + utilizationRate: "0.5", +} as const; + +const integration = Schema.decodeUnknownSync(Integration)({ + actions: [], + id: marketDto.integrationId, + metadata: { + description: "Aave lending and borrowing", + externalLink: "https://aave.com", + logoURI: "https://assets.stakek.it/protocols/aave.svg", + }, + name: "Aave V3", + networks: ["ethereum"], + providerId: "aave", +}); +const market = Schema.decodeUnknownSync(Market)(marketDto); +const address = (suffix: string) => + Schema.decodeSync(WalletAddress)(`0x${suffix.padStart(40, "0")}`); +const t = ((key: string) => key) as TFunction; + +const makePosition = ({ + owner, + supplied, +}: { + readonly owner: WalletAddress; + readonly supplied: string; +}) => { + const [position] = deriveBorrowPositionItems({ + integrationPositions: [ + { + integration, + position: Schema.decodeUnknownSync(BorrowAccountPosition)({ + address: owner, + availableToBorrowUsd: "450", + currentLtv: "0.4", + debtBalances: [ + { + apy: "0.06", + balance: "400", + balanceRaw: "400000000", + balanceUsd: "400", + marketId: market.id, + pendingActions: [ + { + args: { + marketId: market.id, + tokenAddress: market.loanToken.address, + }, + label: "Repay", + type: "repay", + }, + ], + tokenAddress: market.loanToken.address, + tokenSymbol: market.loanToken.symbol, + }, + ], + healthFactor: "2.125", + integrationId: integration.id, + netApy: "-0.006", + netWorthUsd: "600", + network: market.network, + supplyBalances: [ + { + apy: "0.02", + balance: supplied, + balanceRaw: "500000000000000000", + balanceUsd: (Number(supplied) * 2000).toString(), + isCollateral: true, + marketId: market.id, + pendingActions: [ + { + args: { + amountRaw: "500000000000000000", + marketId: market.id, + tokenAddress: market.collateralTokens[0]!.token.address, + }, + label: "Withdraw", + type: "withdraw", + }, + ], + tokenAddress: market.collateralTokens[0]!.token.address, + tokenSymbol: market.collateralTokens[0]!.token.symbol, + }, + ], + totalBorrowedUsd: "400", + totalCollateralUsd: "1000", + totalSuppliedUsd: "1000", + }), + }, + ], + markets: [market], + }); + + if (!position) throw new Error("Expected Borrow position"); + return position; +}; + +const getAction = ( + position: ReturnType, + owner: WalletAddress, + type: BorrowPositionAction["type"] +) => { + const action = getBorrowPositionActions({ address: owner, position, t }).find( + (candidate) => candidate.type === type + ); + if (!action) throw new Error(`Expected ${type} action`); + return action; +}; + +const PositionOutlet = ({ + action, + position, +}: { + readonly action: BorrowPositionAction; + readonly position: ReturnType; +}) => ( + +); + +const renderAction = ({ + action, + position, +}: { + readonly action: BorrowPositionAction; + readonly position: ReturnType; +}) => ( + + + + + + } + path="positions/borrow/:marketId" + > + } + path="action/:actionId" + /> + + + + + + +); + +const enterAmount = async (container: HTMLElement, value: string) => { + const input = container.querySelector( + '[data-testid="number-input"]' + ); + if (!input) throw new Error("Expected amount input"); + const setValue = Object.getOwnPropertyDescriptor( + HTMLInputElement.prototype, + "value" + )?.set; + if (!setValue) throw new Error("Expected native input setter"); + + await act(async () => { + setValue.call(input, value); + input.dispatchEvent(new Event("input", { bubbles: true })); + }); +}; + +describe("Borrow position action wallet ownership", () => { + it("resets a mounted withdraw form when its wallet owner changes", async () => { + const ownerA = address("1"); + const ownerB = address("2"); + const positionA = makePosition({ owner: ownerA, supplied: "0.5" }); + const positionB = makePosition({ owner: ownerB, supplied: "0.2" }); + const actionA = getAction(positionA, ownerA, "withdraw"); + const actionB = getAction(positionB, ownerB, "withdraw"); + const app = await render( + renderAction({ action: actionA, position: positionA }) + ); + + await enterAmount(app.container, "0.1"); + expect( + app.container.querySelector( + '[data-testid="number-input"]' + )?.value + ).toBe("0.1"); + expect(app.container.textContent).toContain("0.5 WETH withdrawable"); + + await app.rerender(renderAction({ action: actionB, position: positionB })); + + expect( + app.container.querySelector( + '[data-testid="number-input"]' + )?.value + ).toBe("0"); + expect(app.container.textContent).toContain("0.2 WETH withdrawable"); + }); + + it("resets a mounted repay form when its wallet owner changes", async () => { + const ownerA = address("1"); + const ownerB = address("2"); + const positionA = makePosition({ owner: ownerA, supplied: "0.5" }); + const positionB = makePosition({ owner: ownerB, supplied: "0.2" }); + const actionA = getAction(positionA, ownerA, "repay"); + const actionB = getAction(positionB, ownerB, "repay"); + const app = await render( + renderAction({ action: actionA, position: positionA }) + ); + + await enterAmount(app.container, "25"); + expect( + app.container.querySelector( + '[data-testid="number-input"]' + )?.value + ).toBe("25"); + + await app.rerender(renderAction({ action: actionB, position: positionB })); + + expect( + app.container.querySelector( + '[data-testid="number-input"]' + )?.value + ).toBe("0"); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/borrow-position-details.browser.test.tsx b/packages/widget/tests/pages-dashboard/borrow-position-details.browser.test.tsx new file mode 100644 index 000000000..5d5c6b7f4 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/borrow-position-details.browser.test.tsx @@ -0,0 +1,453 @@ +import { HttpResponse, http } from "msw"; +import { userEvent } from "vitest/browser"; +import type { BorrowAccountPosition } from "../../src/domain/borrow/position"; +import { borrowApiRoute } from "../mocks/api-routes"; +import { rkMockWallet } from "../utils/mock-connector"; +import { describe, expect, it } from "../utils/test-extend"; +import { renderApp } from "../utils/test-utils"; + +const account = "0x0000000000000000000000000000000000000001"; + +type PositionDto = typeof BorrowAccountPosition.Encoded; + +const integration = { + id: "aave-borrow", + providerId: "aave", + name: "Aave V3 Borrow", + networks: ["ethereum"], + metadata: { + description: "Aave lending and borrowing", + externalLink: "https://aave.com", + logoURI: "https://assets.stakek.it/protocols/aave.svg", + }, + actions: [], +} as const; + +const market = { + id: "aave-v3-ethereum-usdc", + integrationId: integration.id, + network: "ethereum", + type: "pool", + poolAddress: "0x0000000000000000000000000000000000000001", + loanToken: { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + collateralTokens: [ + { + token: { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + priceUsd: "2000", + maxLtv: "0.8", + liquidationThreshold: "0.85", + liquidationPenalty: "0.05", + supplyRate: "0.02", + }, + ], + borrowRate: "0.06", + totalSupply: "1000000", + totalSupplyRaw: "1000000000000", + totalBorrow: "500000", + totalBorrowRaw: "500000000000", + availableLiquidity: "500000", + availableLiquidityRaw: "500000000000", + utilizationRate: "0.5", + loanTokenPriceUsd: "1", + isBorrowEnabled: true, + supplyCollateralFeeBps: "0", + feeWrapperAddress: null, + minLoan: null, +} as const; + +const position: PositionDto = { + address: account, + availableToBorrowUsd: "450", + currentLtv: "0.4", + debtBalances: [ + { + apy: "0.06", + balance: "400", + balanceRaw: "400000000", + balanceUsd: "400", + marketId: market.id, + pendingActions: [ + { + args: { + marketId: market.id, + tokenAddress: market.loanToken.address, + }, + label: "Repay", + type: "repay", + }, + ], + tokenAddress: market.loanToken.address, + tokenSymbol: "USDC", + }, + ], + healthFactor: "2.125", + integrationId: integration.id, + netApy: "-0.006", + netWorthUsd: "600", + network: "ethereum", + supplyBalances: [ + { + apy: "0.02", + balance: "0.5", + balanceRaw: "500000000000000000", + balanceUsd: "1000", + isCollateral: true, + marketId: market.id, + pendingActions: [ + { + args: { + amountRaw: "500000000000000000", + marketId: market.id, + tokenAddress: market.collateralTokens[0].token.address, + }, + label: "Withdraw", + type: "withdraw", + }, + { + args: { + marketId: market.id, + tokenAddress: market.collateralTokens[0].token.address, + }, + label: "Disable collateral", + type: "disableCollateral", + }, + ], + positionState: { + availableToBorrowUsd: "450", + currentLtv: "0.4", + healthFactor: "2.125", + liquidationThreshold: "0.85", + }, + tokenAddress: market.collateralTokens[0].token.address, + tokenSymbol: "WETH", + }, + ], + totalBorrowedUsd: "400", + totalCollateralUsd: "1000", + totalSuppliedUsd: "1000", +}; + +const emptyPosition: PositionDto = { + ...position, + availableToBorrowUsd: "0", + currentLtv: "0", + debtBalances: [], + healthFactor: null, + netApy: "0", + netWorthUsd: "0", + supplyBalances: [], + totalBorrowedUsd: "0", + totalCollateralUsd: "0", + totalSuppliedUsd: "0", +}; + +describe("Borrow position details", () => { + it("does not present market liquidity or collateral APY as user capacity", async ({ + worker, + }) => { + const marketWithCollateralChoices = { + ...market, + collateralTokens: [ + market.collateralTokens[0], + { + ...market.collateralTokens[0], + supplyRate: "0.03", + token: { + address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + decimals: 8, + name: "Wrapped Bitcoin", + symbol: "WBTC", + }, + }, + ], + }; + const usdtMarket = { + ...marketWithCollateralChoices, + id: "aave-v3-ethereum-usdt", + loanToken: { + address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + decimals: 6, + name: "Tether USD", + symbol: "USDT", + }, + }; + + worker.use( + http.get(borrowApiRoute("/v1/integrations"), () => + HttpResponse.json([integration]) + ), + http.get(borrowApiRoute("/v1/markets"), () => + HttpResponse.json({ + items: [marketWithCollateralChoices, usdtMarket], + limit: 100, + offset: 0, + total: 2, + }) + ), + http.get(borrowApiRoute("/v1/positions"), () => + HttpResponse.json(emptyPosition) + ) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: rkMockWallet({ accounts: [account] }), + }, + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + + await userEvent.click(app.getByText("Borrow")); + await expect.element(app.getByText("Borrow APY")).toBeInTheDocument(); + expect(app.getByText("Supply APY").length).toBe(0); + + const borrowSection = app.container.querySelector( + '[data-rk="borrow-amount-section"]' + ); + expect(borrowSection?.textContent).not.toContain("available"); + expect(borrowSection?.textContent).not.toContain("Max"); + + await app.getByTestId("borrow-market-select").click(); + await app.getByTestId("borrow-market-select__group_usdc").click(); + expect(app.container.textContent).not.toContain("Max:"); + await app + .getByTestId( + `borrow-market-select__item_${marketWithCollateralChoices.id}` + ) + .click(); + + await app.getByTestId("borrow-collateral-select").click(); + expect(app.container.textContent).not.toContain("2%"); + expect(app.container.textContent).not.toContain("3%"); + + app.unmount(); + }); + + it("renders borrow positions in Manage and opens borrow details", async ({ + worker, + }) => { + worker.use( + http.get(borrowApiRoute("/v1/integrations"), () => + HttpResponse.json([integration]) + ), + http.get(borrowApiRoute("/v1/markets"), () => + HttpResponse.json({ + items: [market], + limit: 100, + offset: 0, + total: 1, + }) + ), + http.get(borrowApiRoute("/v1/positions"), () => + HttpResponse.json(position) + ) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: rkMockWallet({ accounts: [account] }), + }, + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + + await userEvent.click(app.getByText("Manage")); + + await expect.element(app.getByText("Total supplied")).toBeInTheDocument(); + await expect.element(app.getByText("Net worth")).toBeInTheDocument(); + await expect.element(app.getByText("WETH/USDC")).toBeInTheDocument(); + await expect.element(app.getByText("Aave V3")).toBeInTheDocument(); + + await userEvent.click(app.getByText("WETH/USDC")); + + await expect.element(app.getByText("Borrow details")).toBeInTheDocument(); + await expect.element(app.getByText("Health factor")).toBeInTheDocument(); + await expect.element(app.getByText("2.125")).toBeInTheDocument(); + await expect.element(app.getByText("Loan to value")).toBeInTheDocument(); + await expect.element(app.getByText("Collateral value")).toBeInTheDocument(); + await expect.element(app.getByText("Borrow APY")).toBeInTheDocument(); + expect(app.container.textContent).toContain("Ethereum"); + expect(app.container.textContent).toContain("$400.00"); + await expect + .element(app.getByTestId("borrow-position-action__repay")) + .toBeInTheDocument(); + await expect + .element(app.getByTestId("borrow-position-action__withdraw")) + .toBeInTheDocument(); + await expect + .element(app.getByTestId("borrow-position-action__disableCollateral")) + .toBeInTheDocument(); + + await app.getByTestId("borrow-position-action__withdraw").click(); + + await expect + .element(app.getByText("0.5 WETH withdrawable")) + .toBeInTheDocument(); + await expect.element(app.getByText("Borrow details")).toBeInTheDocument(); + + await userEvent.click(app.getByTestId("number-input")); + await userEvent.keyboard("0.1"); + await app.getByRole("button", { name: "Review borrow" }).click(); + + await expect.element(app.getByText("Review borrow")).toBeInTheDocument(); + await expect + .element(app.getByRole("button", { name: "Back to position" })) + .toBeInTheDocument(); + + const backToPosition = app.container.querySelector( + '[aria-label="Back to position"]' + ); + expect(backToPosition).not.toBeNull(); + await expect.element(app.getByText("Borrow details")).toBeInTheDocument(); + + backToPosition!.click(); + + await expect.element(app.getByText("Actions")).toBeInTheDocument(); + + const breadcrumbBack = app.container.querySelector( + '[data-testid="borrow-position-details-back"]' + ); + + expect(breadcrumbBack).not.toBeNull(); + + breadcrumbBack!.click(); + + await expect.element(app.getByText("My positions")).toBeInTheDocument(); + await expect.element(app.getByText("Total supplied")).toBeInTheDocument(); + + app.unmount(); + }); + + it("keeps a position execution route mounted while its position refreshes", async ({ + worker, + }) => { + let positionRequests = 0; + let releasePositionRefresh!: () => void; + let releaseActionStatus!: () => void; + const positionRefresh = new Promise((resolve) => { + releasePositionRefresh = resolve; + }); + const actionStatus = new Promise((resolve) => { + releaseActionStatus = resolve; + }); + const transaction = { + address: account, + chainId: "1", + id: "withdraw-transaction", + network: "ethereum", + status: "BROADCASTED", + type: "WITHDRAW", + } as const; + const action = { + action: "withdraw", + address: account, + createdAt: "2026-01-01T00:00:00.000Z", + currentStep: 1, + hasNextStep: false, + id: "withdraw-action", + integrationId: integration.id, + status: "CREATED", + totalSteps: 1, + transactions: [transaction], + } as const; + + worker.use( + http.get(borrowApiRoute("/v1/integrations"), () => + HttpResponse.json([integration]) + ), + http.get(borrowApiRoute("/v1/markets"), () => + HttpResponse.json({ + items: [market], + limit: 100, + offset: 0, + total: 1, + }) + ), + http.get(borrowApiRoute("/v1/positions"), async () => { + positionRequests += 1; + if (positionRequests === 1) { + return HttpResponse.json(position); + } + if (positionRequests === 2) { + await positionRefresh; + } + return HttpResponse.json(emptyPosition); + }), + http.post(borrowApiRoute("/v1/actions"), () => HttpResponse.json(action)), + http.get(borrowApiRoute(`/v1/actions/${action.id}`), async () => { + await actionStatus; + return HttpResponse.json({ + ...action, + status: "SUCCESS", + transactions: [{ ...transaction, status: "CONFIRMED" }], + }); + }) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: rkMockWallet({ accounts: [account] }), + }, + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + + await userEvent.click(app.getByText("Manage")); + await userEvent.click(app.getByText("WETH/USDC")); + await app.getByTestId("borrow-position-action__withdraw").click(); + await userEvent.click(app.getByTestId("number-input")); + await userEvent.keyboard("0.1"); + await app.getByRole("button", { name: "Review borrow" }).click(); + await app.getByRole("button", { name: "Confirm" }).click(); + + await expect + .poll( + () => + app.container.querySelector('[data-rk="borrow-steps-page"]') !== null + ) + .toBe(true); + + releaseActionStatus(); + await expect + .poll(() => positionRequests, { timeout: 5_000 }) + .toBeGreaterThanOrEqual(2); + await expect + .poll( + () => + app.container.querySelector('[data-rk="borrow-complete-page"]') !== + null + ) + .toBe(true); + + releasePositionRefresh(); + await expect.element(app.getByText("Something went wrong")).toBeVisible(); + await expect + .poll( + () => + app.container.querySelector('[data-rk="borrow-complete-page"]') !== + null + ) + .toBe(true); + + app.unmount(); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/earn-details-model.test.tsx b/packages/widget/tests/pages-dashboard/earn-details-model.test.tsx index 69d699733..18b4c05b7 100644 --- a/packages/widget/tests/pages-dashboard/earn-details-model.test.tsx +++ b/packages/widget/tests/pages-dashboard/earn-details-model.test.tsx @@ -1,8 +1,10 @@ import type { TFunction } from "i18next"; import { describe, expect, it } from "vitest"; -import type { Yield } from "../../src/domain/types/yields"; -import { getEarnDetailsModel } from "../../src/pages-dashboard/overview/earn-details/earn-details-model"; +import type { EarnYieldWithProvider } from "../../src/domain/schema/earn-models"; + +import { getEarnDetailsModel } from "../../src/features/earn/ui/dashboard/earn-details/earn-details-model"; import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; +import { decodeValidator } from "../utils/validators"; const t = (key: string, options?: Record): string => { const translations: Record = { @@ -23,12 +25,14 @@ const minStakeMechanics = { entryLimits: { minimum: "1", maximum: null, subsequentMinimum: null }, }; -const makeYield = (overrides?: Partial): Yield => +const makeYield = ( + overrides?: Partial +): EarnYieldWithProvider => ({ ...yieldApiYieldFixture(), provider: { name: "Midas" }, ...overrides, - }) as Yield; + }) as EarnYieldWithProvider; describe("getEarnDetailsModel", () => { it("includes price per share in details when yield state provides it", () => { @@ -124,14 +128,18 @@ describe("getEarnDetailsModel", () => { }); it("uses all selected validators in the header provider name", () => { - const firstValidator = yieldApiValidatorFixture({ - address: "validator-1", - name: "Kiln", - }); - const secondValidator = yieldApiValidatorFixture({ - address: "validator-2", - name: "P2P", - }); + const firstValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + name: "Kiln", + }) + ); + const secondValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-2", + name: "P2P", + }) + ); const model = getEarnDetailsModel({ selectedValidators: new Map([ diff --git a/packages/widget/tests/pages-dashboard/manage-state.test.ts b/packages/widget/tests/pages-dashboard/manage-state.test.ts new file mode 100644 index 000000000..a4deaf4b9 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/manage-state.test.ts @@ -0,0 +1,98 @@ +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { describe, expect, it } from "vitest"; +import type { Position as BorrowPosition } from "../../src/domain/borrow/position"; +import { getUnifiedManagePositionsState } from "../../src/features/portfolio/ui/dashboard/positions/model"; + +const borrowPositionsResult = (positions: ReadonlyArray = []) => + AsyncResult.success, unknown>(positions); + +const getState = ( + overrides: Partial[0]> = {} +) => + getUnifiedManagePositionsState({ + borrowPositionsResult: borrowPositionsResult(), + borrowWalletIsConnected: true, + earnIsError: false, + earnIsFetching: false, + earnIsLoading: false, + earnPositionsCount: 0, + isConnected: true, + isConnecting: false, + showEarnPositions: true, + ...overrides, + }); + +describe("unified Manage positions state", () => { + it("supports earn-only positions", () => { + expect( + getState({ + earnPositionsCount: 2, + }) + ).toMatchObject({ + hasPartialError: false, + showEmptyPositions: false, + showPositionsList: true, + totalPositionsCount: 2, + }); + }); + + it("supports borrow-only positions", () => { + expect( + getState({ + borrowPositionsResult: borrowPositionsResult([{} as BorrowPosition]), + showEarnPositions: false, + }) + ).toMatchObject({ + showEmptyPositions: false, + showPositionsList: true, + totalPositionsCount: 1, + }); + }); + + it("supports mixed earn and borrow positions", () => { + expect( + getState({ + borrowPositionsResult: borrowPositionsResult([{} as BorrowPosition]), + earnPositionsCount: 2, + }).totalPositionsCount + ).toBe(3); + }); + + it("shows empty state only after both sources resolve without positions", () => { + expect(getState()).toMatchObject({ + showEmptyPositions: true, + totalPositionsCount: 0, + }); + }); + + it("surfaces partial errors while preserving available positions", () => { + expect( + getState({ + borrowPositionsResult: AsyncResult.fail< + unknown, + ReadonlyArray + >("borrow failed"), + earnPositionsCount: 1, + }) + ).toMatchObject({ + hasOnlyErrors: false, + hasPartialError: true, + showPositionsList: true, + totalPositionsCount: 1, + }); + }); + + it("keeps loading state from either module from becoming final empty", () => { + expect( + getState({ + borrowPositionsResult: AsyncResult.initial< + ReadonlyArray, + unknown + >(true), + }) + ).toMatchObject({ + isAnyPositionsLoading: true, + showEmptyPositions: false, + }); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/position-action-wallet-scope.dom.test.tsx b/packages/widget/tests/pages-dashboard/position-action-wallet-scope.dom.test.tsx new file mode 100644 index 000000000..c63a7ba61 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/position-action-wallet-scope.dom.test.tsx @@ -0,0 +1,129 @@ +import { Schema } from "effect"; +import { act } from "react"; +import { describe, expect, it } from "vitest"; +import { EarnBalance } from "../../src/domain/schema/earn-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { PositionDetailsWorkflowKey } from "../../src/features/position-details/state/workflow"; +import { useValidatorAddressesHandling } from "../../src/features/position-details/ui/classic/hooks/use-validator-addresses-handling"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture, yieldBalanceFixture } from "../fixtures"; +import { render } from "../utils/test-utils.dom"; + +const address = (suffix: string) => + Schema.decodeSync(WalletAddress)(`0x${suffix.padStart(40, "0")}`); + +const scopeA = new WalletScopeKey({ + address: address("1"), + network: "ethereum", +}); +const scopeB = new WalletScopeKey({ + address: address("2"), + network: "ethereum", +}); +const refreshedScopeA = new WalletScopeKey({ + additionalAddresses: { binanceBeaconAddress: "bnb-refreshed" }, + address: scopeA.address, + network: scopeA.network, +}); +const yieldDto = yieldApiYieldFixture(); +const yieldBalance = Schema.decodeUnknownSync(EarnBalance)( + yieldBalanceFixture({ + address: scopeA.address, + pendingActions: [ + { + arguments: { + fields: [ + { + label: "Validator", + name: "validatorAddress", + required: true, + type: "address", + }, + ], + }, + intent: "manage", + passthrough: "wallet-a-action", + type: "CLAIM_REWARDS", + }, + ], + token: yieldDto.token, + }) +); +const pendingAction = yieldBalance.pendingActions[0]!; + +const ValidatorModalHarness = ({ + scope, +}: { + readonly scope: WalletScopeKey; +}) => { + const modal = useValidatorAddressesHandling( + new PositionDetailsWorkflowKey({ + balanceId: null, + integrationId: null, + pendingActionType: null, + scope, + }) + ); + + return ( + <> + + {modal.showValidatorsModal ? "open" : "closed"} + + + {modal.showValidatorsModal + ? modal.pendingActionDto?.passthrough + : "none"} + + + + ); +}; + +describe("position action wallet ownership", () => { + it("closes a validator-required pending action when its wallet owner changes", async () => { + const app = await render(); + + await act(async () => + app.container.querySelector("button")?.click() + ); + expect( + app.container.querySelector('[data-testid="modal-state"]')?.textContent + ).toBe("open"); + expect( + app.container.querySelector('[data-testid="payload"]')?.textContent + ).toBe("wallet-a-action"); + + await app.rerender(); + + expect( + app.container.querySelector('[data-testid="modal-state"]')?.textContent + ).toBe("closed"); + expect( + app.container.querySelector('[data-testid="payload"]')?.textContent + ).toBe("none"); + }); + + it("keeps validator selection open when only additional addresses refresh", async () => { + const app = await render(); + + await act(async () => + app.container.querySelector("button")?.click() + ); + await app.rerender(); + + expect( + app.container.querySelector('[data-testid="modal-state"]')?.textContent + ).toBe("open"); + expect( + app.container.querySelector('[data-testid="payload"]')?.textContent + ).toBe("wallet-a-action"); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/position-details-action-tabs.browser.test.tsx b/packages/widget/tests/pages-dashboard/position-details-action-tabs.browser.test.tsx new file mode 100644 index 000000000..0164a8d70 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/position-details-action-tabs.browser.test.tsx @@ -0,0 +1,137 @@ +import { I18nextProvider } from "react-i18next"; +import { + MemoryRouter, + Route, + Routes, + useLocation, + useNavigate, +} from "react-router"; +import { describe, expect, it } from "vitest"; +import { userEvent } from "vitest/browser"; +import { shouldRegisterDashboardEarnFooterButton } from "../../src/app/routes/dashboard-routes"; +import { PositionDetailsActionTabs } from "../../src/features/position-details/ui/dashboard/components/position-details-action-tabs"; +import { i18nInstance } from "../../src/translation"; +import { render } from "../utils/test-utils"; + +const LocationProbe = () => { + const location = useLocation(); + + return
{location.pathname}
; +}; + +const BackProbe = () => { + const navigate = useNavigate(); + + return ( + + ); +}; + +const renderTabs = (initialEntries: string | string[]) => { + const entries = Array.isArray(initialEntries) + ? initialEntries + : [initialEntries]; + + return render( + + + + + + + + + + + + } + /> + + + + + } + /> + + + + ); +}; + +describe("position details action tabs", () => { + it("registers the earn CTA only for stake form routes", () => { + expect(shouldRegisterDashboardEarnFooterButton("/")).toBe(true); + expect( + shouldRegisterDashboardEarnFooterButton("/positions/yield-1/balance-1") + ).toBe(true); + expect( + shouldRegisterDashboardEarnFooterButton( + "/positions/yield-1/balance-1/stake" + ) + ).toBe(true); + + expect(shouldRegisterDashboardEarnFooterButton("/review")).toBe(false); + expect(shouldRegisterDashboardEarnFooterButton("/steps")).toBe(false); + expect( + shouldRegisterDashboardEarnFooterButton( + "/positions/yield-1/balance-1/stake/review" + ) + ).toBe(false); + }); + + it("renders Stake and Unstake tabs without adding tab changes to history", async () => { + const app = await renderTabs(["/manage", "/positions/yield-1/balance-1"]); + + await expect + .element(app.getByTestId("position-details-action-tab-stake")) + .toBeInTheDocument(); + await expect + .element(app.getByTestId("position-details-action-tab-unstake")) + .toBeInTheDocument(); + await expect + .element(app.getByTestId("location")) + .toHaveTextContent("/positions/yield-1/balance-1"); + + await userEvent.click( + app.getByTestId("position-details-action-tab-unstake") + ); + + await expect + .element(app.getByTestId("location")) + .toHaveTextContent("/positions/yield-1/balance-1/unstake"); + + await userEvent.click(app.getByTestId("back")); + + await expect + .element(app.getByTestId("location")) + .toHaveTextContent("/manage"); + }); + + it("does not render a selector when there is only one available action", async () => { + const app = await render( + + + + + } + /> + + + + ); + + expect(app.container.textContent).not.toContain("Stake"); + expect(app.container.textContent).not.toContain("Unstake"); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/position-details-action-tabs.test.tsx b/packages/widget/tests/pages-dashboard/position-details-action-tabs.test.tsx deleted file mode 100644 index 2cb7d1b25..000000000 --- a/packages/widget/tests/pages-dashboard/position-details-action-tabs.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { I18nextProvider } from "react-i18next"; -import { - MemoryRouter, - Route, - Routes, - useLocation, - useNavigate, -} from "react-router"; -import { userEvent } from "vitest/browser"; -import { shouldRegisterDashboardEarnFooterButton } from "../../src/Dashboard"; -import { getPositionDetailsStakeReviewPath } from "../../src/hooks/navigation/use-position-details-stake-match"; -import { PositionDetailsActionTabs } from "../../src/pages-dashboard/position-details/components/position-details-action-tabs"; -import { i18nInstance } from "../../src/translation"; -import { describe, expect, it } from "../utils/test-extend"; -import { render } from "../utils/test-utils"; - -const LocationProbe = () => { - const location = useLocation(); - - return
{location.pathname}
; -}; - -const BackProbe = () => { - const navigate = useNavigate(); - - return ( - - ); -}; - -const renderTabs = (initialEntries: string | string[]) => { - const entries = Array.isArray(initialEntries) - ? initialEntries - : [initialEntries]; - - return render( - - - - - - - - - - - - } - /> - - - - - } - /> - - - - ); -}; - -describe("position details action tabs", () => { - it("builds the nested stake review path from position route params", () => { - expect( - getPositionDetailsStakeReviewPath({ - balanceId: "balance-1", - integrationId: "yield-1", - }) - ).toBe("/positions/yield-1/balance-1/stake/review"); - - expect( - getPositionDetailsStakeReviewPath({ - balanceId: "balance-1", - }) - ).toBeNull(); - }); - - it("registers the earn CTA only for stake form routes", () => { - expect(shouldRegisterDashboardEarnFooterButton("/")).toBe(true); - expect( - shouldRegisterDashboardEarnFooterButton("/positions/yield-1/balance-1") - ).toBe(true); - expect( - shouldRegisterDashboardEarnFooterButton( - "/positions/yield-1/balance-1/stake" - ) - ).toBe(true); - - expect(shouldRegisterDashboardEarnFooterButton("/review")).toBe(false); - expect(shouldRegisterDashboardEarnFooterButton("/steps")).toBe(false); - expect( - shouldRegisterDashboardEarnFooterButton( - "/positions/yield-1/balance-1/stake/review" - ) - ).toBe(false); - }); - - it("renders Stake and Unstake tabs without adding tab changes to history", async () => { - const app = await renderTabs(["/manage", "/positions/yield-1/balance-1"]); - - await expect - .element(app.getByTestId("position-details-action-tab-stake")) - .toBeInTheDocument(); - await expect - .element(app.getByTestId("position-details-action-tab-unstake")) - .toBeInTheDocument(); - await expect - .element(app.getByTestId("location")) - .toHaveTextContent("/positions/yield-1/balance-1"); - - await userEvent.click( - app.getByTestId("position-details-action-tab-unstake") - ); - - await expect - .element(app.getByTestId("location")) - .toHaveTextContent("/positions/yield-1/balance-1/unstake"); - - await userEvent.click(app.getByTestId("back")); - - await expect - .element(app.getByTestId("location")) - .toHaveTextContent("/manage"); - }); - - it("does not render a selector when there is only one available action", async () => { - const app = await render( - - - - - } - /> - - - - ); - - expect(app.container.textContent).not.toContain("Stake"); - expect(app.container.textContent).not.toContain("Unstake"); - }); -}); diff --git a/packages/widget/tests/pages-dashboard/position-details-model.test.tsx b/packages/widget/tests/pages-dashboard/position-details-model.test.tsx index 3cde165a0..4734eb238 100644 --- a/packages/widget/tests/pages-dashboard/position-details-model.test.tsx +++ b/packages/widget/tests/pages-dashboard/position-details-model.test.tsx @@ -1,12 +1,14 @@ import BigNumber from "bignumber.js"; +import { Schema } from "effect"; import type { TFunction } from "i18next"; import { describe, expect, it } from "vitest"; +import { RewardsSummary } from "../../src/domain/schema/dashboard-models"; +import type { EarnYieldWithProvider } from "../../src/domain/schema/earn-models"; +import { EarnBalance } from "../../src/domain/schema/earn-models"; +import { TokenAddress } from "../../src/domain/schema/identifiers"; import type { PositionBalancesByType } from "../../src/domain/types/positions"; -import type { Yield } from "../../src/domain/types/yields"; -import { - type DashboardPositionPendingAction, - getDashboardPositionDetailsModel, -} from "../../src/pages-dashboard/position-details/position-details-model"; + +import { getDashboardPositionDetailsModel } from "../../src/features/position-details/ui/dashboard/position-details-model"; import { yieldApiProviderFixture, yieldApiYieldFixture, @@ -14,6 +16,13 @@ import { yieldRewardRateFixture, } from "../fixtures"; +type DashboardPositionPendingAction = Parameters< + typeof getDashboardPositionDetailsModel +>[0]["pendingActions"][number]; + +const makeBalance = (overrides?: Parameters[0]) => + Schema.decodeUnknownSync(EarnBalance)(yieldBalanceFixture(overrides)); + const t = (key: string, options?: Record): string => { const translations: Record = { "dashboard.earn_details.asset": `Asset (${options?.symbol ?? ""})`, @@ -58,7 +67,9 @@ const t = (key: string, options?: Record): string => { return translations[key] ?? key; }; -const makeYield = (overrides?: Partial): Yield => +const makeYield = ( + overrides?: Partial +): EarnYieldWithProvider => ({ ...yieldApiYieldFixture({ rewardRate: yieldRewardRateFixture({ total: 0.04 }), @@ -74,7 +85,7 @@ const makeYield = (overrides?: Partial): Yield => }), provider: yieldApiProviderFixture({ name: "Rocket Pool" }), ...overrides, - }) as Yield; + }) as EarnYieldWithProvider; const makePositionBalances = (): PositionBalancesByType => { const token = yieldApiYieldFixture().token; @@ -84,7 +95,7 @@ const makePositionBalances = (): PositionBalancesByType => { "active", [ { - ...yieldBalanceFixture({ + ...makeBalance({ amount: "12", amountUsd: "41400", token, @@ -98,7 +109,7 @@ const makePositionBalances = (): PositionBalancesByType => { "claimable", [ { - ...yieldBalanceFixture({ + ...makeBalance({ amount: "0.25", amountUsd: "862", token, @@ -112,7 +123,7 @@ const makePositionBalances = (): PositionBalancesByType => { "locked", [ { - ...yieldBalanceFixture({ + ...makeBalance({ amount: "42", amountUsd: null, token: { ...token, isPoints: true, symbol: "PTS" }, @@ -153,14 +164,14 @@ describe("getDashboardPositionDetailsModel", () => { model.metricCards.find((card) => card.id === "balance") ).toMatchObject({ label: "Balance", - subValue: "$41,400", + subValue: "$41.4K", value: "12 ETH", }); expect( model.metricCards.find((card) => card.id === "rewards") ).toMatchObject({ label: "Rewards", - subValue: "$862", + subValue: "$862.00", value: "0.25 ETH", }); expect(model.metricCards.find((card) => card.id === "apy")).toMatchObject({ @@ -186,7 +197,7 @@ describe("getDashboardPositionDetailsModel", () => { passthrough: "claim", type: "CLAIM_REWARDS", }, - yieldBalance: yieldBalanceFixture({ type: "claimable" }), + yieldBalance: makeBalance({ type: "claimable" }), }, ]; @@ -222,7 +233,7 @@ describe("getDashboardPositionDetailsModel", () => { "active", [ { - ...yieldBalanceFixture({ + ...makeBalance({ amount: "12", amountUsd: "41400", token, @@ -241,7 +252,7 @@ describe("getDashboardPositionDetailsModel", () => { ...makeYield().mechanics, cooldownPeriod: { seconds: 7 * 24 * 60 * 60 }, }, - } as Partial), + } as Partial), pendingActions: [], personalizedRewardRate: null, positionBalancesByType, @@ -279,7 +290,7 @@ describe("getDashboardPositionDetailsModel", () => { positionBalancesByType: makePositionBalances(), providersDetails: [{ name: "Rocket Pool", status: "active" }], reducedStakedOrLiquidBalance: null, - rewardsSummary: { + rewardsSummary: Schema.decodeUnknownSync(RewardsSummary)({ rewards: { last24H: "0", last30D: "0", @@ -288,7 +299,7 @@ describe("getDashboardPositionDetailsModel", () => { total: "1.5", }, token: yieldApiYieldFixture().token, - }, + }), t: t as TFunction, }); @@ -296,13 +307,13 @@ describe("getDashboardPositionDetailsModel", () => { { id: "active-ETH-0", label: "Active", - subValue: "$41,400", + subValue: "$41.4K", value: "12 ETH", }, { id: "claimable-ETH-1", label: "Claimable", - subValue: "$862", + subValue: "$862.00", value: "0.25 ETH", }, { @@ -357,7 +368,9 @@ describe("getDashboardPositionDetailsModel", () => { }, outputToken: { ...baseYield.token, - address: "0x0000000000000000000000000000000000000002", + address: Schema.decodeSync(TokenAddress)( + "0x0000000000000000000000000000000000000002" + ), symbol: "mUSDC", }, state: { @@ -369,7 +382,9 @@ describe("getDashboardPositionDetailsModel", () => { }, token: { ...baseYield.token, - address: "0x0000000000000000000000000000000000000001", + address: Schema.decodeSync(TokenAddress)( + "0x0000000000000000000000000000000000000001" + ), symbol: "USDC", }, }), diff --git a/packages/widget/tests/pages-dashboard/position-details-yield-entry.browser.test.tsx b/packages/widget/tests/pages-dashboard/position-details-yield-entry.browser.test.tsx new file mode 100644 index 000000000..617a4fb93 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/position-details-yield-entry.browser.test.tsx @@ -0,0 +1,213 @@ +import { RegistryProvider, useAtomSet, useAtomValue } from "@effect/atom-react"; +import BigNumber from "bignumber.js"; +import { Layer, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { Navigate, Route, Routes } from "react-router"; +import { RouterProvider } from "react-router/dom"; +import { mainnet } from "viem/chains"; +import { describe, expect, it } from "vitest"; +import { userEvent } from "vitest/browser"; +import type { Connector } from "wagmi"; +import { ApplicationRouteContentProvider } from "../../src/app/composition/application-route-content"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import { + applicationRouterAtom, + applicationRouterRuntime, +} from "../../src/app/runtime/application-router-runtime"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + classicFlowSessionStore, + makeClassicTransactionFlowDestination, +} from "../../src/features/classic-transaction-flow/facade"; +import { + PositionBalancesKey, + positionBalancesByTypeAtom, + tokenBalancesScanAtom, +} from "../../src/features/portfolio/public-state"; +import { + positionDetailsStakeViewAtom, + setPositionDetailsStakeAmountAtom, + submitPositionDetailsStakeAtom, +} from "../../src/features/position-details/ui/dashboard/state/stake-facade"; +import { PositionDetailsStakeEntryKey } from "../../src/features/position-details/ui/dashboard/state/stake-machine"; +import { + walletConnectionStateAtom, + walletScopeAtom, +} from "../../src/features/wallet/public-state"; +import { + YieldOpportunityKey, + yieldOpportunityAtom, +} from "../../src/resources/yield-opportunity/provider"; +import { ApplicationRouter } from "../../src/services/navigation/application-router"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import type { NormalizedWalletState } from "../../src/services/wallet/domain/state"; +import { yieldApiYieldFixture } from "../fixtures"; +import { render } from "../utils/test-utils"; + +const address = Schema.decodeSync(WalletAddress)( + "0x1234567890123456789012345678901234567890" +); +const walletScope = new WalletScopeKey({ + address, + network: "ethereum", +}); +const selectedYield = yieldApiYieldFixture(); +const entryKey = new PositionDetailsStakeEntryKey({ + balanceId: "balance-1", + integrationId: selectedYield.id, + walletScope, +}); +const connectedWalletState = { + additionalAddresses: null, + address, + chain: mainnet, + connector: { id: "test", uid: "test" } as Connector, + connectorChains: [mainnet], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum", + status: "connected", +} satisfies NormalizedWalletState; + +const PositionEntry = () => { + const view = useAtomValue(positionDetailsStakeViewAtom(entryKey)); + const setAmount = useAtomSet(setPositionDetailsStakeAmountAtom(entryKey)); + const submit = useAtomSet(submitPositionDetailsStakeAtom(entryKey)); + + return ( + <> + + + + ); +}; + +const ReviewGuard = () => { + const session = useAtomValue(classicFlowSessionStore.currentSessionAtom); + return session ? ( +
Position stake review
+ ) : ( + + ); +}; + +const Runtime = () => { + return ( + + } + /> + } + /> + Missing Flow Session} /> + + ); +}; + +const Router = () => { + const router = useAtomValue(applicationRouterAtom); + + return ( + }> + + + ); +}; + +const TestApp = () => { + const positionKey = new PositionBalancesKey({ + balanceId: entryKey.balanceId, + scope: walletScope, + yieldId: entryKey.integrationId, + }); + + return ( + + + + ); +}; + +describe("position-details Yield Entry", () => { + it("runs the production position facade through atom-owned Review navigation", async () => { + const app = await render(); + + await userEvent.click(app.getByTestId("position-stake-amount")); + await expect + .element(app.getByTestId("position-stake-submit")) + .toBeEnabled(); + await userEvent.click(app.getByTestId("position-stake-submit")); + + await expect + .element(app.getByText("Position stake review")) + .toBeInTheDocument(); + expect(app.container.textContent).not.toContain("Missing Flow Session"); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/position-wallet-scope.dom.test.tsx b/packages/widget/tests/pages-dashboard/position-wallet-scope.dom.test.tsx new file mode 100644 index 000000000..b6bfd01f3 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/position-wallet-scope.dom.test.tsx @@ -0,0 +1,192 @@ +import { + RegistryProvider, + useAtom, + useAtomSet, + useAtomValue, +} from "@effect/atom-react"; +import BigNumber from "bignumber.js"; +import { Option, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import { act } from "react"; +import { describe, expect, it } from "vitest"; +import { EarnPosition } from "../../src/domain/schema/earn-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + PositionBalancesKey, + positionBalancesAtom, + positionBalancesByTypeAtom, +} from "../../src/features/portfolio/resources/positions"; +import { + PositionDetailsWorkflowKey, + positionDetailsWorkflowAtom, +} from "../../src/features/position-details/state/workflow"; +import { yieldPositionsResourceAtom } from "../../src/resources/yield-positions/yield-positions"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture, yieldBalanceFixture } from "../fixtures"; +import { render } from "../utils/test-utils.dom"; + +const address = (suffix: string) => + Schema.decodeSync(WalletAddress)(`0x${suffix.padStart(40, "0")}`); +const scopeA = new WalletScopeKey({ + address: address("1"), + network: "ethereum", +}); +const scopeB = new WalletScopeKey({ + address: address("2"), + network: "ethereum", +}); +const yieldDto = yieldApiYieldFixture(); +const position = Schema.decodeUnknownSync(EarnPosition)({ + balances: [ + yieldBalanceFixture({ + address: scopeA.address, + amount: "5", + amountRaw: "5000000000000000000", + amountUsd: "25", + pendingActions: [ + { + amount: "5", + arguments: { fields: [] }, + intent: "manage", + passthrough: "wallet-a-action", + type: "CLAIM_REWARDS", + }, + ], + token: yieldDto.token, + type: "active", + }), + ], + outputTokenBalance: null, + yieldId: yieldDto.id, +}); + +const stagedActionAttemptsAtom = Atom.make>([]).pipe( + Atom.keepAlive +); + +const PositionRouteHarness = ({ + scope, +}: { + readonly scope: WalletScopeKey; +}) => { + const key = new PositionBalancesKey({ + balanceId: "default", + scope, + yieldId: position.yieldId, + }); + const balancesResult = useAtomValue(positionBalancesAtom(key)); + const balances = Option.getOrNull(AsyncResult.value(balancesResult)); + const balancesByType = Option.getOrNull( + AsyncResult.value(useAtomValue(positionBalancesByTypeAtom(key))) + ); + const [workflow, setWorkflow] = useAtom( + positionDetailsWorkflowAtom( + new PositionDetailsWorkflowKey({ + balanceId: "default", + integrationId: position.yieldId, + pendingActionType: null, + scope, + }) + ) + ); + const setAttempts = useAtomSet(stagedActionAttemptsAtom); + const attempts = useAtomValue(stagedActionAttemptsAtom); + const activeBalance = balancesByType?.get("active")?.[0] ?? null; + const stage = (kind: string) => { + if (balances) { + setAttempts((current) => [...current, kind]); + } + }; + + return ( + <> + + {activeBalance?.amount.toFixed() ?? "empty"} + + + {activeBalance?.tokenPriceInUsd.toFixed() ?? "empty"} + + + {activeBalance?.pendingActions.length ?? 0} + + {workflow.unstakeAmount.toFixed()} + {attempts.join(",") || "none"} + + + + + ); +}; + +describe("dashboard position wallet ownership", () => { + it("clears wallet A data and action state before wallet B can stage an action", async () => { + const resourceA = yieldPositionsResourceAtom(scopeA); + const resourceB = yieldPositionsResourceAtom(scopeB); + const wrapper = (scope: WalletScopeKey) => ( + + + + ); + const app = await render(wrapper(scopeA)); + + expect( + app.container.querySelector('[data-testid="balance"]')?.textContent + ).toBe("5"); + expect( + app.container.querySelector('[data-testid="price"]')?.textContent + ).toBe("25"); + expect( + app.container.querySelector('[data-testid="pending"]')?.textContent + ).toBe("1"); + await act(async () => + app.container.querySelector("button")?.click() + ); + expect( + app.container.querySelector('[data-testid="amount"]')?.textContent + ).toBe("5"); + + await app.rerender(wrapper(scopeB)); + + expect( + app.container.querySelector('[data-testid="balance"]')?.textContent + ).toBe("empty"); + expect( + app.container.querySelector('[data-testid="price"]')?.textContent + ).toBe("empty"); + expect( + app.container.querySelector('[data-testid="pending"]')?.textContent + ).toBe("0"); + expect( + app.container.querySelector('[data-testid="amount"]')?.textContent + ).toBe("0"); + + const buttons = app.container.querySelectorAll("button"); + await act(async () => { + buttons[1]?.click(); + buttons[2]?.click(); + }); + expect( + app.container.querySelector('[data-testid="attempts"]')?.textContent + ).toBe("none"); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/provider-selection-card.browser.test.tsx b/packages/widget/tests/pages-dashboard/provider-selection-card.browser.test.tsx new file mode 100644 index 000000000..bbeade834 --- /dev/null +++ b/packages/widget/tests/pages-dashboard/provider-selection-card.browser.test.tsx @@ -0,0 +1,271 @@ +import BigNumber from "bignumber.js"; +import { I18nextProvider } from "react-i18next"; +import { describe, expect, it, vi } from "vitest"; +import { userEvent } from "vitest/browser"; +import type { EarnYieldWithProvider } from "../../src/domain/schema/earn-models"; + +import type { useSelectValidator } from "../../src/features/earn/ui/classic/earn-page/components/select-validator-section/use-select-validator"; +import { SelectYieldRewardDetails } from "../../src/features/earn/ui/classic/earn-page/components/select-yield-section/select-yield-reward-details"; +import { ProviderSelectionCard } from "../../src/features/earn/ui/dashboard/earn-details/components/provider-selection-card"; +import { i18nInstance } from "../../src/translation"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; +import { render } from "../utils/test-utils"; +import { decodeValidator } from "../utils/validators"; +import { TestWidgetConfigProvider } from "../utils/widget-config-provider"; + +const hookState = vi.hoisted(() => ({ + entryView: {} as Record, + selectValidator: undefined as unknown as ReturnType< + typeof useSelectValidator + >, +})); + +vi.mock( + "../../src/features/earn/ui/classic/earn-page/components/select-validator-section/use-select-validator", + () => ({ + useSelectValidator: () => hookState.selectValidator, + }) +); + +vi.mock( + "../../src/features/earn/react/use-earn-facades", + async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useEarnEntry: () => ({ view: hookState.entryView }), + }; + } +); + +const baseYield = yieldApiYieldFixture(); +const multiSelectStake = { + ...baseYield, + mechanics: { + ...baseYield.mechanics, + requiresValidatorSelection: true, + arguments: { + ...baseYield.mechanics.arguments, + enter: { + fields: { + validatorAddresses: { + required: true, + }, + }, + }, + }, + }, +} satisfies EarnYieldWithProvider; + +const createHookValue = ( + overrides: Partial> = {} +): ReturnType => ({ + hasMoreValidators: false, + isLoading: false, + isLoadingMoreValidators: false, + onClose: vi.fn(), + onItemClick: vi.fn(), + onLoadMoreValidators: vi.fn(), + onOpen: vi.fn(), + onRemoveValidator: vi.fn(), + onValidatorSearch: vi.fn(), + onViewMoreClick: vi.fn(), + selectedStake: multiSelectStake, + selectedValidators: new Map(), + validatorSearch: "", + validatorsData: [], + ...overrides, +}); + +const renderProviderSelectionCard = () => + render( + + + + + + ); + +const renderSelectYieldRewardDetails = ({ + dashboardVariant = true, +}: { + dashboardVariant?: boolean; +} = {}) => + render( + + + + + + ); + +describe("ProviderSelectionCard", () => { + it("renders all selected validators with removal controls", async () => { + const firstValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + name: "Kiln", + preferred: true, + tvl: "1000000", + }) + ); + const secondValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-2", + name: "P2P", + tvl: "2000000", + }) + ); + const onRemoveValidator = vi.fn(); + + hookState.entryView = { + providers: [], + }; + hookState.selectValidator = createHookValue({ + onRemoveValidator, + selectedValidators: new Map([ + [firstValidator.key, firstValidator], + [secondValidator.key, secondValidator], + ]), + }); + + const app = await renderProviderSelectionCard(); + + await expect.element(app.getByText("Kiln")).toBeInTheDocument(); + await expect.element(app.getByText("P2P")).toBeInTheDocument(); + await expect.element(app.getByText("Preferred")).toBeInTheDocument(); + + const removeP2P = app.container.querySelector('[aria-label="Remove P2P"]'); + expect(removeP2P).not.toBeNull(); + + await userEvent.click(removeP2P as HTMLButtonElement); + + expect(onRemoveValidator).toHaveBeenCalledWith(secondValidator); + }); + + it("maps unknown validator statuses to inactive", async () => { + const validator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + name: "Yuma", + status: "not_found", + }) + ); + + hookState.entryView = { + providers: [], + }; + hookState.selectValidator = createHookValue({ + selectedValidators: new Map([[validator.key, validator]]), + }); + + const app = await renderProviderSelectionCard(); + + await expect.element(app.getByText("Inactive")).toBeInTheDocument(); + }); + + it("keeps the selector available from the multi-validator dashboard card", async () => { + const firstValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + name: "Kiln", + }) + ); + const secondValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-2", + name: "P2P", + }) + ); + + hookState.entryView = { + providers: [], + }; + hookState.selectValidator = createHookValue({ + selectedValidators: new Map([ + [firstValidator.key, firstValidator], + [secondValidator.key, secondValidator], + ]), + validatorSearch: "missing validator", + }); + + const app = await renderProviderSelectionCard(); + + await userEvent.click(app.getByText("Manage validators")); + + await expect + .element(app.getByTestId("select-modal__search-input")) + .toHaveValue("missing validator"); + await expect + .element(app.getByText("No validators found")) + .toBeInTheDocument(); + }); +}); + +describe("SelectYieldRewardDetails", () => { + it("uses all selected validators in the dashboard Stake via summary", async () => { + const firstValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-1", + name: "Kiln", + }) + ); + const secondValidator = decodeValidator( + yieldApiValidatorFixture({ + address: "validator-2", + name: "P2P", + }) + ); + + hookState.entryView = { + estimatedRewards: null, + providers: [], + rewardToken: null, + rewardsTokenSymbol: "ETH", + selectedStake: multiSelectStake, + selectedValidators: new Map([ + [firstValidator.key, firstValidator], + [secondValidator.key, secondValidator], + ]), + stakeAmount: new BigNumber(1), + }; + + const app = await renderSelectYieldRewardDetails(); + + await expect + .element(app.getByText("via Kiln and others")) + .toBeInTheDocument(); + }); + + it("hides the yield strategy summary in the widget variant", async () => { + hookState.entryView = { + estimatedRewards: null, + providers: [], + rewardToken: null, + rewardsTokenSymbol: "ETH", + selectedStake: multiSelectStake, + selectedValidators: new Map(), + stakeAmount: new BigNumber(1), + }; + + const app = await renderSelectYieldRewardDetails({ + dashboardVariant: false, + }); + + await expect + .element(app.getByText("EarnYieldWithProvider strategy")) + .not.toBeInTheDocument(); + }); +}); diff --git a/packages/widget/tests/pages-dashboard/provider-selection-card.test.tsx b/packages/widget/tests/pages-dashboard/provider-selection-card.test.tsx deleted file mode 100644 index bda35e1b4..000000000 --- a/packages/widget/tests/pages-dashboard/provider-selection-card.test.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import BigNumber from "bignumber.js"; -import { Maybe } from "purify-ts"; -import { I18nextProvider } from "react-i18next"; -import { vi } from "vitest"; -import { userEvent } from "vitest/browser"; -import type { Yield } from "../../src/domain/types/yields"; -import type { useSelectValidator } from "../../src/pages/details/earn-page/components/select-validator-section/use-select-validator"; -import { SelectYieldRewardDetails } from "../../src/pages/details/earn-page/components/select-yield-section/select-yield-reward-details"; -import { ProviderSelectionCard } from "../../src/pages-dashboard/overview/earn-details/components/provider-selection-card"; -import { SettingsContextProvider } from "../../src/providers/settings"; -import { i18nInstance } from "../../src/translation"; -import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; -import { describe, expect, it } from "../utils/test-extend"; -import { render } from "../utils/test-utils"; - -const hookState = vi.hoisted(() => ({ - earnContext: {} as Record, - selectValidator: undefined as unknown as ReturnType< - typeof useSelectValidator - >, -})); - -vi.mock( - "../../src/pages/details/earn-page/components/select-validator-section/use-select-validator", - () => ({ - useSelectValidator: () => hookState.selectValidator, - }) -); - -vi.mock( - "../../src/pages/details/earn-page/state/earn-page-context", - async (importOriginal) => { - const actual = await importOriginal(); - - return { - ...actual, - useEarnPageContext: () => hookState.earnContext, - }; - } -); - -const baseYield = yieldApiYieldFixture(); -const multiSelectStake = { - ...baseYield, - mechanics: { - ...baseYield.mechanics, - requiresValidatorSelection: true, - arguments: { - ...baseYield.mechanics.arguments, - enter: { - fields: [ - { - label: "Validators", - name: "validatorAddresses", - required: true, - type: "address", - }, - ], - }, - }, - }, -} as Yield; - -const createHookValue = ( - overrides: Partial> = {} -): ReturnType => ({ - hasMoreValidators: false, - isLoading: false, - isLoadingMoreValidators: false, - onClose: vi.fn(), - onItemClick: vi.fn(), - onLoadMoreValidators: vi.fn(), - onOpen: vi.fn(), - onRemoveValidator: vi.fn(), - onValidatorSearch: vi.fn(), - onViewMoreClick: vi.fn(), - selectedStake: Maybe.of(multiSelectStake), - selectedValidators: new Map(), - validatorSearch: "", - validatorsData: Maybe.of([]), - ...overrides, -}); - -const renderProviderSelectionCard = () => - render( - - - - - - ); - -const renderSelectYieldRewardDetails = ({ - dashboardVariant = true, -}: { - dashboardVariant?: boolean; -} = {}) => - render( - - - - - - ); - -describe("ProviderSelectionCard", () => { - it("renders all selected validators with removal controls", async () => { - const firstValidator = yieldApiValidatorFixture({ - address: "validator-1", - name: "Kiln", - preferred: true, - tvl: "1000000", - }); - const secondValidator = yieldApiValidatorFixture({ - address: "validator-2", - name: "P2P", - tvl: "2000000", - }); - const onRemoveValidator = vi.fn(); - - hookState.earnContext = { - providersDetails: Maybe.of([]), - }; - hookState.selectValidator = createHookValue({ - onRemoveValidator, - selectedValidators: new Map([ - [firstValidator.address, firstValidator], - [secondValidator.address, secondValidator], - ]), - }); - - const app = await renderProviderSelectionCard(); - - await expect.element(app.getByText("Kiln")).toBeInTheDocument(); - await expect.element(app.getByText("P2P")).toBeInTheDocument(); - await expect.element(app.getByText("Preferred")).toBeInTheDocument(); - - const removeP2P = app.container.querySelector('[aria-label="Remove P2P"]'); - expect(removeP2P).not.toBeNull(); - - await userEvent.click(removeP2P as HTMLButtonElement); - - expect(onRemoveValidator).toHaveBeenCalledWith(secondValidator); - }); - - it("maps unknown validator statuses to inactive", async () => { - const validator = yieldApiValidatorFixture({ - address: "validator-1", - name: "Yuma", - status: "not_found", - }); - - hookState.earnContext = { - providersDetails: Maybe.of([]), - }; - hookState.selectValidator = createHookValue({ - selectedValidators: new Map([[validator.address, validator]]), - }); - - const app = await renderProviderSelectionCard(); - - await expect.element(app.getByText("Inactive")).toBeInTheDocument(); - }); - - it("keeps the selector available from the multi-validator dashboard card", async () => { - const firstValidator = yieldApiValidatorFixture({ - address: "validator-1", - name: "Kiln", - }); - const secondValidator = yieldApiValidatorFixture({ - address: "validator-2", - name: "P2P", - }); - - hookState.earnContext = { - providersDetails: Maybe.of([]), - }; - hookState.selectValidator = createHookValue({ - selectedValidators: new Map([ - [firstValidator.address, firstValidator], - [secondValidator.address, secondValidator], - ]), - validatorSearch: "missing validator", - }); - - const app = await renderProviderSelectionCard(); - - await userEvent.click(app.getByText("Manage validators")); - - await expect - .element(app.getByTestId("select-modal__search-input")) - .toHaveValue("missing validator"); - await expect - .element(app.getByText("No validators found")) - .toBeInTheDocument(); - }); -}); - -describe("SelectYieldRewardDetails", () => { - it("uses all selected validators in the dashboard Stake via summary", async () => { - const firstValidator = yieldApiValidatorFixture({ - address: "validator-1", - name: "Kiln", - }); - const secondValidator = yieldApiValidatorFixture({ - address: "validator-2", - name: "P2P", - }); - - hookState.earnContext = { - estimatedRewards: Maybe.empty(), - providersDetails: Maybe.of([]), - rewardToken: Maybe.empty(), - rewardsTokenSymbol: "ETH", - selectedStake: Maybe.of(multiSelectStake), - selectedValidators: new Map([ - [firstValidator.address, firstValidator], - [secondValidator.address, secondValidator], - ]), - stakeAmount: new BigNumber(1), - }; - - const app = await renderSelectYieldRewardDetails(); - - await expect - .element(app.getByText("via Kiln and others")) - .toBeInTheDocument(); - }); - - it("hides the yield strategy summary in the widget variant", async () => { - hookState.earnContext = { - estimatedRewards: Maybe.empty(), - providersDetails: Maybe.of([]), - rewardToken: Maybe.empty(), - rewardsTokenSymbol: "ETH", - selectedStake: Maybe.of(multiSelectStake), - selectedValidators: new Map(), - stakeAmount: new BigNumber(1), - }; - - const app = await renderSelectYieldRewardDetails({ - dashboardVariant: false, - }); - - await expect - .element(app.getByText("Yield strategy")) - .not.toBeInTheDocument(); - }); -}); diff --git a/packages/widget/tests/pages/transaction-workflow-atoms.test.ts b/packages/widget/tests/pages/transaction-workflow-atoms.test.ts new file mode 100644 index 000000000..be926eb4b --- /dev/null +++ b/packages/widget/tests/pages/transaction-workflow-atoms.test.ts @@ -0,0 +1,93 @@ +import { Effect, Layer, Schema, Stream } from "effect"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import type { ActionTransaction } from "../../src/domain/schema/action-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import { makeTransactionWorkflowModule } from "../../src/features/transaction-workflow/state"; +import type { ActionMeta } from "../../src/public-api/types"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { ClassicTransactionWorkflowInput } from "../../src/services/workflow/transaction-workflow-model"; +import { TransactionWorkflowService } from "../../src/services/workflow/transaction-workflow-service"; +import { yieldApiTransactionFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const yieldId = Schema.decodeSync(YieldId)("yield-1"); +const walletScope = new WalletScopeKey({ address, network: "ethereum" }); +const actionMeta = { + actionId: "action-1", + address, +} as unknown as ActionMeta; +const transaction = yieldApiTransactionFixture({ + id: "tx-1", + network: "ethereum", + status: "CREATED", + unsignedTransaction: "unsigned", +}) as ActionTransaction; +const makeInput = () => + new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [transaction], + walletScope, + yieldId, + }); + +describe("transaction workflow module", () => { + it("creates a fresh atom graph for every equal execution input", () => { + const first = makeTransactionWorkflowModule(makeInput()); + const second = makeTransactionWorkflowModule(makeInput()); + + expect(first).not.toBe(second); + }); + + it("disposes the machine with its scope while capabilities remain mounted", async () => { + const probe = { disposed: 0, started: 0 }; + const workflowLayer = Layer.succeed( + TransactionWorkflowService, + TransactionWorkflowService.of({ + make: () => + Effect.acquireRelease( + Effect.sync(() => { + probe.started += 1; + return { + dispatch: () => Effect.void, + events: Stream.never, + states: Stream.never, + }; + }), + () => + Effect.sync(() => { + probe.disposed += 1; + }) + ), + }) + ); + const registry = AtomRegistry.make({ + initialValues: [[walletRuntime.layer, workflowLayer]], + }); + const firstAtom = makeTransactionWorkflowModule(makeInput()); + const secondAtom = makeTransactionWorkflowModule(makeInput()); + + const disposeFirstScope = registry.mount(firstAtom); + const first = registry.get(firstAtom); + const disposeFirstState = registry.mount(first.stateAtom); + const disposeSecondScope = registry.mount(secondAtom); + const second = registry.get(secondAtom); + const disposeSecondEvents = registry.mount(second.eventsAtom); + await vi.waitFor(() => expect(probe.started).toBe(2)); + + disposeFirstScope(); + disposeSecondScope(); + await vi.waitFor(() => expect(probe.disposed).toBe(2)); + + registry.set(first.commandAtom, { _tag: "Retry" }); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(probe.disposed).toBe(2); + expect(probe.started).toBe(2); + + disposeFirstState(); + disposeSecondEvents(); + }); +}); diff --git a/packages/widget/tests/pages/transaction-workflow-classic.browser.test.tsx b/packages/widget/tests/pages/transaction-workflow-classic.browser.test.tsx new file mode 100644 index 000000000..a82a18ab7 --- /dev/null +++ b/packages/widget/tests/pages/transaction-workflow-classic.browser.test.tsx @@ -0,0 +1,319 @@ +import { + make as makeScopedAtom, + RegistryProvider, + useAtomValue, +} from "@effect/atom-react"; +import { Deferred, Effect, Layer, Schema } from "effect"; +import type * as Atom from "effect/unstable/reactivity/Atom"; +import { createContext, useContext } from "react"; +import { MemoryRouter, Outlet, Route, Routes, useNavigate } from "react-router"; +import { describe, expect, it, vi } from "vitest"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import { actionHistoryRevisionAtom } from "../../src/features/classic-transaction-flow/state/action-history"; +import { makeClassicTransactionWorkflowModule } from "../../src/features/classic-transaction-flow/state/classic-transaction-workflow"; +import type { ActionMeta } from "../../src/public-api/types"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { ClassicTransactionWorkflowInput } from "../../src/services/workflow/transaction-workflow-model"; +import { TransactionWorkflowOperationsService } from "../../src/services/workflow/transaction-workflow-operations-service"; +import { TransactionWorkflowService } from "../../src/services/workflow/transaction-workflow-service"; +import { yieldApiTransactionFixture } from "../fixtures"; +import { render } from "../utils/test-utils"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const yieldId = Schema.decodeSync(YieldId)("yield-1"); +const walletScope = new WalletScopeKey({ address, network: "ethereum" }); +const key = new ClassicTransactionWorkflowInput({ + actionMeta: { + actionId: "action-1", + address, + } as unknown as ActionMeta, + transactions: [ + yieldApiTransactionFixture({ + id: "tx-1", + network: "ethereum", + status: "CREATED", + unsignedTransaction: "unsigned-payload", + }), + ], + walletScope, + yieldId, +}); +const WorkflowScopedAtom = makeScopedAtom( + (workflowKey: ClassicTransactionWorkflowInput) => + makeClassicTransactionWorkflowModule(workflowKey) +); +const useWorkflowScopedAtom = WorkflowScopedAtom.use; +type WorkflowModule = Atom.Type< + ReturnType +>; +const WorkflowContext = createContext(null); + +const ClassicTransactionWorkflowRoute = ({ + workflowKey, +}: { + readonly workflowKey: ClassicTransactionWorkflowInput; +}) => ( + + + +); + +const ClassicTransactionWorkflowBinding = () => { + const workflowAtom = useWorkflowScopedAtom(); + const workflow = useAtomValue(workflowAtom); + + return ( + + + + ); +}; + +const WorkflowProbe = () => { + const workflow = useContext(WorkflowContext); + if (!workflow) throw new Error("Expected a workflow"); + const view = useAtomValue(workflow.viewAtom); + const actionHistoryRevision = useAtomValue(actionHistoryRevisionAtom); + if (!view) throw new Error("Expected workflow input"); + const { state } = view; + const navigate = useNavigate(); + + return ( + <> +
{state._tag}
+
+ {actionHistoryRevision === 0 ? "unchanged" : "changed"} +
+ + + ); +}; + +describe("classic transaction workflow browser integration", () => { + it("starts automatically and completes through the derived workflow atom", async () => { + const signTransaction = vi.fn(() => + Effect.succeed({ + broadcasted: false as const, + signedTx: "signed-payload", + }) + ); + const submitClassicSigned = vi.fn(() => Effect.void); + const operations = { + completeWorkflow: () => Effect.void, + getBorrowAction: () => Effect.die("unexpected borrow status"), + getClassicStatus: () => + Effect.succeed({ + explorerUrl: "https://explorer.test/tx", + status: "CONFIRMED" as const, + }), + getWalletState: Effect.succeed({ + address, + network: "ethereum", + status: "connected", + }), + signMessage: () => Effect.die("unexpected message signing"), + signTransaction, + stepBorrowAction: () => Effect.die("unexpected borrow step"), + submitBorrowTransaction: () => Effect.die("unexpected borrow submission"), + submitClassicHash: () => Effect.die("unexpected hash submission"), + submitClassicSigned, + submitWorkflow: () => Effect.void, + trackEvent: () => Effect.void, + } as unknown as TransactionWorkflowOperationsService["Service"]; + const workflowLayer = TransactionWorkflowService.layer.pipe( + Layer.provide( + Layer.succeed(TransactionWorkflowOperationsService, operations) + ) + ); + const app = await render( + + + + } + > + } /> + + + + + ); + + await expect + .element(app.getByTestId("state")) + .toHaveTextContent("Completed"); + await expect + .element(app.getByTestId("action-history")) + .toHaveTextContent("changed"); + expect(signTransaction).toHaveBeenCalledOnce(); + expect(submitClassicSigned).toHaveBeenCalledOnce(); + + app.unmount(); + }); + + it("eventually interrupts deferred signing when the steps route unmounts", async () => { + const signing = await Effect.runPromise( + Deferred.make<{ broadcasted: boolean; signedTx: string }>() + ); + const signingInterrupted = await Effect.runPromise(Deferred.make()); + const signTransaction = vi.fn(() => + Deferred.await(signing).pipe( + Effect.onInterrupt(() => + Deferred.succeed(signingInterrupted, undefined) + ) + ) + ); + const submitClassicSigned = vi.fn(() => Effect.void); + const operations = { + completeWorkflow: () => Effect.void, + getBorrowAction: () => Effect.die("unexpected borrow status"), + getClassicStatus: () => Effect.die("unexpected confirmation"), + getWalletState: Effect.succeed({ + address, + network: "ethereum", + status: "connected", + }), + signMessage: () => Effect.die("unexpected message signing"), + signTransaction, + stepBorrowAction: () => Effect.die("unexpected borrow step"), + submitBorrowTransaction: () => Effect.die("unexpected borrow submission"), + submitClassicHash: () => Effect.die("unexpected hash submission"), + submitClassicSigned, + submitWorkflow: () => Effect.void, + trackEvent: () => Effect.void, + } as unknown as TransactionWorkflowOperationsService["Service"]; + const workflowLayer = TransactionWorkflowService.layer.pipe( + Layer.provide( + Layer.succeed(TransactionWorkflowOperationsService, operations) + ) + ); + const app = await render( + + + + } + > + } /> + + home} /> + + + + ); + + await expect.element(app.getByTestId("state")).toHaveTextContent("Signing"); + expect(signTransaction).toHaveBeenCalledOnce(); + await app.getByRole("button", { name: "Leave workflow" }).click(); + await expect.element(app.getByTestId("home")).toHaveTextContent("home"); + await Effect.runPromise(Deferred.await(signingInterrupted)); + + expect(submitClassicSigned).not.toHaveBeenCalled(); + expect(signTransaction).toHaveBeenCalledOnce(); + + app.unmount(); + }); + + it("interrupts deferred confirmation when the steps route unmounts", async () => { + const confirmation = await Effect.runPromise( + Deferred.make<{ explorerUrl: string; status: string }>() + ); + const confirmationInterrupted = await Effect.runPromise( + Deferred.make() + ); + const twoTransactionKey = new ClassicTransactionWorkflowInput({ + ...key, + transactions: [ + ...key.transactions, + yieldApiTransactionFixture({ + id: "tx-2", + network: "ethereum", + status: "CREATED", + unsignedTransaction: "second-unsigned-payload", + }), + ], + }); + const signTransaction = vi.fn(() => + Effect.succeed({ + broadcasted: false as const, + signedTx: "signed-payload", + }) + ); + const submitClassicSigned = vi.fn(() => Effect.void); + const getClassicStatus = vi.fn(() => + Deferred.await(confirmation).pipe( + Effect.onInterrupt(() => + Deferred.succeed(confirmationInterrupted, undefined) + ) + ) + ); + const operations = { + completeWorkflow: () => Effect.void, + getBorrowAction: () => Effect.die("unexpected borrow status"), + getClassicStatus, + getWalletState: Effect.succeed({ + address, + network: "ethereum", + status: "connected", + }), + signMessage: () => Effect.die("unexpected message signing"), + signTransaction, + stepBorrowAction: () => Effect.die("unexpected borrow step"), + submitBorrowTransaction: () => Effect.die("unexpected borrow submission"), + submitClassicHash: () => Effect.die("unexpected hash submission"), + submitClassicSigned, + submitWorkflow: () => Effect.void, + trackEvent: () => Effect.void, + } as unknown as TransactionWorkflowOperationsService["Service"]; + const workflowLayer = TransactionWorkflowService.layer.pipe( + Layer.provide( + Layer.succeed(TransactionWorkflowOperationsService, operations) + ) + ); + const app = await render( + + + + + } + > + } /> + + home} /> + + + + ); + + await expect + .element(app.getByTestId("state")) + .toHaveTextContent("Confirming"); + expect(signTransaction).toHaveBeenCalledOnce(); + expect(submitClassicSigned).toHaveBeenCalledOnce(); + expect(getClassicStatus).toHaveBeenCalledOnce(); + + await app.getByRole("button", { name: "Leave workflow" }).click(); + await expect.element(app.getByTestId("home")).toHaveTextContent("home"); + await Effect.runPromise(Deferred.await(confirmationInterrupted)); + expect(signTransaction).toHaveBeenCalledOnce(); + expect(submitClassicSigned).toHaveBeenCalledOnce(); + expect(getClassicStatus).toHaveBeenCalledOnce(); + + app.unmount(); + }); +}); diff --git a/packages/widget/tests/pages/transaction-workflow-model.test.ts b/packages/widget/tests/pages/transaction-workflow-model.test.ts new file mode 100644 index 000000000..42bebc656 --- /dev/null +++ b/packages/widget/tests/pages/transaction-workflow-model.test.ts @@ -0,0 +1,332 @@ +import { Equal, Schema } from "effect"; +import { describe, expect, it } from "vitest"; +import { Action } from "../../src/domain/borrow/action"; +import { Transaction } from "../../src/domain/borrow/transaction"; +import type { ActionTransaction } from "../../src/domain/schema/action-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import type { ActionMeta } from "../../src/public-api/types"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { + appendTransactionWorkflowBatch, + BorrowTransactionWorkflowInput, + ClassicTransactionWorkflowInput, + getCurrentTransactionWorkflowTransaction, + getTransactionWorkflowAction, + initializeTransactionWorkflow, + makeBorrowTransactionWorkflowBatch, + TransactionAdvanceError, + TransactionConfirmationError, + TransactionSignError, + TransactionSubmissionError, + updateCurrentTransactionWorkflowTransaction, + validateTransactionWorkflowInput, +} from "../../src/services/workflow/transaction-workflow-model"; +import { yieldApiTransactionFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const yieldId = Schema.decodeSync(YieldId)("yield-1"); +const classicWalletScope = new WalletScopeKey({ + address, + network: "ethereum", +}); +const borrowWalletScope = new WalletScopeKey({ address, network: "base" }); +const actionMeta = { + actionId: "action-1", + address, +} as unknown as ActionMeta; + +const classicTransaction = ( + id: string, + status: ActionTransaction["status"], + stepIndex: number +) => + yieldApiTransactionFixture({ + id, + network: "ethereum", + status, + stepIndex, + }); + +const borrowTransaction = (id: string, status = "WAITING_FOR_SIGNATURE") => + Schema.decodeUnknownSync(Transaction)({ + address, + chainId: "8453", + id, + network: "base", + signablePayload: JSON.stringify({ + data: "0xabcdef", + from: address, + gasLimit: "21000", + to: "0x0000000000000000000000000000000000000002", + }), + signingFormat: "EVM_TRANSACTION", + status, + type: "BORROW", + }); + +const borrowAction = ({ + currentStep = 1, + status = "CREATED", + totalSteps = 2, + transactions = [borrowTransaction("borrow-1")], +}: { + readonly currentStep?: number; + readonly status?: string; + readonly totalSteps?: number; + readonly transactions?: ReadonlyArray; +} = {}) => + Schema.decodeUnknownSync(Action)({ + address, + action: "borrow", + createdAt: "2026-07-10T12:00:00.000Z", + currentStep, + hasNextStep: currentStep < totalSteps, + id: "borrow-action-1", + integrationId: "morpho-blue", + status, + totalSteps, + transactions: transactions.map((transaction) => + Schema.encodeSync(Transaction)(transaction) + ), + }); + +describe("transaction workflow model", () => { + it("captures immutable inputs while retaining structural equality", () => { + const classicInput = { + actionMeta, + transactions: [classicTransaction("classic-1", "CREATED", 0)], + walletScope: classicWalletScope, + yieldId, + }; + const action = borrowAction(); + + const classic = new ClassicTransactionWorkflowInput(classicInput); + const borrow = new BorrowTransactionWorkflowInput({ + action, + walletScope: borrowWalletScope, + }); + + expect( + Equal.equals( + classic, + new ClassicTransactionWorkflowInput({ ...classicInput }) + ) + ).toBe(true); + expect( + Equal.equals( + new BorrowTransactionWorkflowInput({ + action, + walletScope: borrowWalletScope, + }), + new BorrowTransactionWorkflowInput({ + action, + walletScope: borrowWalletScope, + }) + ) + ).toBe(true); + expect(classic.transactions).not.toBe(classicInput.transactions); + expect(classic.walletScope).not.toBe(classicWalletScope); + expect(borrow.action).not.toBe(action); + expect(borrow.walletScope).not.toBe(borrowWalletScope); + }); + + it("rejects transactions outside the captured wallet network", () => { + const input = new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [ + yieldApiTransactionFixture({ + id: "wrong-network", + network: "base", + status: "CREATED", + }), + ], + walletScope: classicWalletScope, + yieldId, + }); + + expect(validateTransactionWorkflowInput(input)?._tag).toBe( + "TransactionWorkflowInputError" + ); + }); + + it("sorts a fixed classic batch and selects its first incomplete transaction", () => { + const state = initializeTransactionWorkflow( + new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [ + classicTransaction("third", "CREATED", 3), + classicTransaction("first", "CONFIRMED", 1), + classicTransaction("second", "SKIPPED", 2), + ], + walletScope: classicWalletScope, + yieldId, + }) + ); + + expect(state._tag).toBe("Signing"); + expect( + state.context.batches[0]?.transactions.map( + ({ source }) => source.transaction.id + ) + ).toEqual(["first", "second", "third"]); + expect( + getCurrentTransactionWorkflowTransaction(state.context)?.source + .transaction.id + ).toBe("third"); + }); + + it("starts broadcast transactions in confirmation and disables completed fixed batches", () => { + const makeKey = (status: ActionTransaction["status"]) => + new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("classic-1", status, 0)], + walletScope: classicWalletScope, + yieldId, + }); + + expect(initializeTransactionWorkflow(makeKey("BROADCASTED"))._tag).toBe( + "Confirming" + ); + expect(initializeTransactionWorkflow(makeKey("CONFIRMED"))._tag).toBe( + "Disabled" + ); + expect( + initializeTransactionWorkflow( + new BorrowTransactionWorkflowInput({ + action: borrowAction({ status: "SUCCESS" }), + walletScope: borrowWalletScope, + }) + )._tag + ).toBe("Completed"); + }); + + it("updates the current transaction without mutating prior context", () => { + const initial = initializeTransactionWorkflow( + new BorrowTransactionWorkflowInput({ + action: borrowAction(), + walletScope: borrowWalletScope, + }) + ).context; + const updated = updateCurrentTransactionWorkflowTransaction({ + context: initial, + update: (transaction) => ({ + ...transaction, + meta: { ...transaction.meta, done: true }, + }), + }); + + expect(initial.batches[0]?.transactions[0]?.meta.done).toBe(false); + expect(updated.batches[0]?.transactions[0]?.meta.done).toBe(true); + }); + + it("appends a borrow batch while retaining history and deduplicating a server step", () => { + const firstAction = borrowAction(); + const initial = initializeTransactionWorkflow( + new BorrowTransactionWorkflowInput({ + action: firstAction, + walletScope: borrowWalletScope, + }) + ).context; + const nextAction = borrowAction({ + currentStep: 2, + transactions: [borrowTransaction("borrow-2")], + }); + const batch = makeBorrowTransactionWorkflowBatch(nextAction); + const appended = appendTransactionWorkflowBatch({ + batch, + context: initial, + domain: { _tag: "Borrow", action: nextAction }, + }); + const deduplicated = appendTransactionWorkflowBatch({ + batch, + context: appended, + domain: { _tag: "Borrow", action: nextAction }, + }); + + expect(appended.batches.map(({ id }) => id)).toEqual([ + "borrow-step-1", + "borrow-step-2", + ]); + expect(appended.batches[0]).toBe(initial.batches[0]); + expect(deduplicated.batches).toHaveLength(2); + expect( + getCurrentTransactionWorkflowTransaction(deduplicated)?.source.transaction + .id + ).toBe("borrow-2"); + }); + + it("allows the one retry command only from the matching failed phase", () => { + const context = initializeTransactionWorkflow( + new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("classic-1", "CREATED", 0)], + walletScope: classicWalletScope, + yieldId, + }) + ).context; + const common = { + batchId: "classic", + message: "failed", + transactionId: "classic-1", + workflowId: "action-1", + }; + const states = [ + { + state: { + _tag: "SignFailed", + context, + error: new TransactionSignError({ + ...common, + customMessage: null, + network: "ethereum", + }), + } as const, + action: "sign", + }, + { + state: { + _tag: "SubmissionFailed", + context, + error: new TransactionSubmissionError({ + ...common, + broadcasted: false, + }), + } as const, + action: "submit", + }, + { + state: { + _tag: "ConfirmationFailed", + context, + error: new TransactionConfirmationError({ + ...common, + network: "ethereum", + }), + } as const, + action: "confirm", + }, + { + state: { + _tag: "AdvanceFailed", + context, + error: new TransactionAdvanceError(common), + } as const, + action: "advance", + }, + ]; + + for (const { action, state } of states) { + expect( + getTransactionWorkflowAction({ command: { _tag: "Retry" }, state }) + ).toBe(action); + } + expect( + getTransactionWorkflowAction({ + command: { _tag: "Retry" }, + state: { _tag: "Signing", context }, + }) + ).toBeNull(); + }); +}); diff --git a/packages/widget/tests/pages/transaction-workflow-runtime.test.ts b/packages/widget/tests/pages/transaction-workflow-runtime.test.ts new file mode 100644 index 000000000..402b0659b --- /dev/null +++ b/packages/widget/tests/pages/transaction-workflow-runtime.test.ts @@ -0,0 +1,869 @@ +import { Deferred, Effect, Fiber, Layer, Option, Schema, Stream } from "effect"; +import { TestClock } from "effect/testing"; +import { describe, expect, it, vi } from "vitest"; +import { Action } from "../../src/domain/borrow/action"; +import { Transaction } from "../../src/domain/borrow/transaction"; +import type { ActionTransaction } from "../../src/domain/schema/action-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import type { ActionMeta } from "../../src/public-api/types"; +import { + ActivityInvalidationKey, + SingleYieldBalancesInvalidationKey, + WalletBalancesInvalidationKey, + YieldPositionsInvalidationKey, +} from "../../src/services/resource-invalidation"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { + BorrowTransactionWorkflowInput, + ClassicTransactionWorkflowInput, + type TransactionWorkflowState, +} from "../../src/services/workflow/transaction-workflow-model"; +import { + getTransactionWorkflowSubmissionInvalidationKeys, + TransactionWorkflowOperationsService, +} from "../../src/services/workflow/transaction-workflow-operations-service"; +import { + type TransactionWorkflowHandle, + TransactionWorkflowService, +} from "../../src/services/workflow/transaction-workflow-service"; +import { yieldApiTransactionFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const otherAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000009" +); +const yieldId = Schema.decodeSync(YieldId)("yield-1"); +const classicWalletScope = new WalletScopeKey({ + address, + network: "ethereum", +}); +const borrowWalletScope = new WalletScopeKey({ address, network: "base" }); +const signedPayload = "0xsigned-payload"; +const transactionHash = + "0x1111111111111111111111111111111111111111111111111111111111111111"; +const actionMeta = { + actionId: "classic-action-1", + actionType: "stake", + address, + amount: "1", + inputToken: undefined, + providersDetails: [], + yieldId, +} as unknown as ActionMeta; + +type TransactionWorkflowOperations = + TransactionWorkflowOperationsService["Service"]; + +const classicTransaction = ( + id: string, + overrides: Partial = {} +) => + yieldApiTransactionFixture({ + id, + network: "ethereum", + status: "CREATED", + stepIndex: 0, + unsignedTransaction: "unsigned-payload", + ...overrides, + }); + +const borrowTransaction = ( + id: string, + overrides: Record = {} +) => + Schema.decodeUnknownSync(Transaction)({ + address, + chainId: "8453", + id, + network: "base", + signablePayload: JSON.stringify({ + data: "0xabcdef", + from: address, + gasLimit: "21000", + to: "0x0000000000000000000000000000000000000002", + value: "0", + }), + signingFormat: "EVM_TRANSACTION", + status: "WAITING_FOR_SIGNATURE", + type: "BORROW", + ...overrides, + }); + +const borrowAction = ({ + currentStep = 1, + hasNextStep = false, + id = "borrow-action-1", + status = "CREATED", + totalSteps = 1, + transactions = [borrowTransaction("borrow-1")], +}: { + readonly currentStep?: number; + readonly hasNextStep?: boolean; + readonly id?: string; + readonly status?: string; + readonly totalSteps?: number; + readonly transactions?: ReadonlyArray; +} = {}) => + Schema.decodeUnknownSync(Action)({ + address, + action: "borrow", + createdAt: "2026-07-10T12:00:00.000Z", + currentStep, + hasNextStep, + id, + integrationId: "morpho-blue", + status, + totalSteps, + transactions: transactions.map((transaction) => + Schema.encodeSync(Transaction)(transaction) + ), + }); + +const walletState = ( + network: "base" | "ethereum", + walletAddress = address +) => ({ + address: walletAddress, + network, + status: "connected", +}); + +const makeOperations = ( + overrides: Partial> = {} +): TransactionWorkflowOperations => + ({ + completeWorkflow: () => Effect.void, + getBorrowAction: () => Effect.succeed(null), + getClassicStatus: () => + Effect.succeed({ + explorerUrl: "https://explorer.test/tx", + status: "CONFIRMED", + }), + getWalletState: Effect.succeed(walletState("ethereum")), + signMessage: () => Effect.succeed(signedPayload), + signTransaction: () => + Effect.succeed({ broadcasted: false, signedTx: signedPayload }), + stepBorrowAction: () => Effect.die("unexpected borrow step"), + submitBorrowTransaction: () => + Effect.succeed({ + link: "https://explorer.test/borrow", + status: "BROADCASTED", + transactionHash, + }), + submitClassicHash: () => Effect.void, + submitClassicSigned: () => Effect.void, + submitWorkflow: () => Effect.void, + trackEvent: () => Effect.void, + ...overrides, + }) as unknown as TransactionWorkflowOperations; + +const makeWorkflowFromService = ({ + key, + operations, +}: { + readonly key: + | ClassicTransactionWorkflowInput + | BorrowTransactionWorkflowInput; + readonly operations: TransactionWorkflowOperations; +}) => + TransactionWorkflowService.use(({ make }) => make(key)).pipe( + Effect.provide( + TransactionWorkflowService.layer.pipe( + Layer.provide( + Layer.succeed(TransactionWorkflowOperationsService, operations) + ) + ) + ) + ); + +const waitForState = ( + machine: TransactionWorkflowHandle, + predicate: (state: TransactionWorkflowState) => boolean +) => + machine.states.pipe( + Stream.filter(predicate), + Stream.runHead, + Effect.forkChild + ); + +const runToCompletion = ( + key: ClassicTransactionWorkflowInput | BorrowTransactionWorkflowInput, + operations: TransactionWorkflowOperations +) => + Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ key, operations }); + const completed = yield* waitForState( + machine, + (state) => state._tag === "Completed" + ); + const finalState = Option.getOrThrow(yield* Fiber.join(completed)); + const events = yield* machine.events.pipe( + Stream.takeUntil( + (event) => event._tag === "TransactionWorkflowCompleted" + ), + Stream.runCollect + ); + + return { events: Array.from(events), finalState }; + }) + ) + ); + +describe("transaction workflow runtime", () => { + it("invalidates a submitted classic workflow before confirmation", async () => { + const getClassicStatus = vi.fn(() => Effect.never); + const submittedKey = new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("submitted-before-route-exit")], + walletScope: classicWalletScope, + yieldId, + }); + let invalidatedKeys: ReadonlyArray = []; + const submitWorkflow = vi.fn( + (workflowKey: ClassicTransactionWorkflowInput) => + Effect.sync(() => { + invalidatedKeys = + getTransactionWorkflowSubmissionInvalidationKeys(workflowKey); + }) + ); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* makeWorkflowFromService({ + key: submittedKey, + operations: makeOperations({ + getClassicStatus, + submitClassicSigned: () => Effect.void, + submitWorkflow, + }), + }); + + yield* Effect.promise(() => + vi.waitFor(() => + expect(submitWorkflow).toHaveBeenCalledWith(submittedKey) + ) + ); + }) + ) + ); + + expect(invalidatedKeys).toEqual([ + new WalletBalancesInvalidationKey({ scope: classicWalletScope }), + new YieldPositionsInvalidationKey({ scope: classicWalletScope }), + new SingleYieldBalancesInvalidationKey({ + address: classicWalletScope.address, + }), + new ActivityInvalidationKey({ scope: classicWalletScope }), + ]); + expect(getClassicStatus).toHaveBeenCalledOnce(); + }); + + it("awaits semantic invalidation before completion and skips it on failure", async () => { + const invalidationStarted = await Effect.runPromise( + Deferred.make() + ); + const invalidationRelease = await Effect.runPromise(Deferred.make()); + const completedKey = new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("awaited-invalidation")], + walletScope: classicWalletScope, + yieldId, + }); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: completedKey, + operations: makeOperations({ + completeWorkflow: ( + workflowKey: ClassicTransactionWorkflowInput + ) => + Deferred.succeed(invalidationStarted, workflowKey).pipe( + Effect.andThen(Deferred.await(invalidationRelease)) + ), + }), + }); + const completed = yield* waitForState( + machine, + (state) => state._tag === "Completed" + ); + + expect(yield* Deferred.await(invalidationStarted)).toEqual( + completedKey + ); + expect(completed.pollUnsafe()).toBeUndefined(); + + yield* Deferred.succeed(invalidationRelease, undefined); + expect(Option.getOrThrow(yield* Fiber.join(completed))._tag).toBe( + "Completed" + ); + }) + ) + ); + + const completeWorkflow = vi.fn(() => Effect.void); + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("failed-before-invalidation")], + walletScope: classicWalletScope, + yieldId, + }), + operations: makeOperations({ + completeWorkflow, + signTransaction: () => Effect.fail(new Error("rejected")), + }), + }); + const failed = yield* waitForState( + machine, + (state) => state._tag === "SignFailed" + ); + yield* Fiber.join(failed); + }) + ) + ); + + expect(completeWorkflow).not.toHaveBeenCalled(); + }); + + it("auto-starts classic signed-payload and broadcast submission paths", async () => { + const submitSigned = vi.fn(() => Effect.void); + const signed = await runToCompletion( + new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("classic-signed")], + walletScope: classicWalletScope, + yieldId, + }), + makeOperations({ submitClassicSigned: submitSigned }) + ); + + expect(submitSigned).toHaveBeenCalledWith({ + payload: { signedTransaction: signedPayload }, + transactionId: "classic-signed", + }); + expect(signed.events.map(({ _tag }) => _tag)).toEqual([ + "TransactionWorkflowSigned", + "TransactionWorkflowSubmitted", + "TransactionWorkflowCompleted", + ]); + + const submitHash = vi.fn(() => Effect.void); + await runToCompletion( + new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("classic-broadcast")], + walletScope: classicWalletScope, + yieldId, + }), + makeOperations({ + signTransaction: () => + Effect.succeed({ broadcasted: true, signedTx: transactionHash }), + submitClassicHash: submitHash, + }) + ); + + expect(submitHash).toHaveBeenCalledWith({ + payload: { hash: transactionHash }, + transactionId: "classic-broadcast", + }); + }); + + it("uses one phase-accurate retry and ignores duplicate stale retries", async () => { + let signAttempts = 0; + let submitAttempts = 0; + const operations = makeOperations({ + signTransaction: () => + Effect.suspend(() => { + signAttempts += 1; + return signAttempts === 1 + ? Effect.fail(new Error("sign failed")) + : Effect.succeed({ + broadcasted: false as const, + signedTx: signedPayload, + }); + }) as never, + submitClassicSigned: () => + Effect.suspend(() => { + submitAttempts += 1; + return submitAttempts === 1 + ? Effect.fail(new Error("submit failed")) + : Effect.void; + }), + }); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("retry")], + walletScope: classicWalletScope, + yieldId, + }), + operations, + }); + const signFailed = yield* waitForState( + machine, + (state) => state._tag === "SignFailed" + ); + yield* Fiber.join(signFailed); + + const submitFailed = yield* waitForState( + machine, + (state) => state._tag === "SubmissionFailed" + ); + yield* machine.dispatch({ _tag: "Retry" }); + yield* machine.dispatch({ _tag: "Retry" }); + yield* Fiber.join(submitFailed); + expect({ signAttempts, submitAttempts }).toEqual({ + signAttempts: 2, + submitAttempts: 1, + }); + + const completed = yield* waitForState( + machine, + (state) => state._tag === "Completed" + ); + yield* machine.dispatch({ _tag: "Retry" }); + yield* Fiber.join(completed); + }) + ) + ); + + expect({ signAttempts, submitAttempts }).toEqual({ + signAttempts: 2, + submitAttempts: 2, + }); + }); + + it("validates wallet identity and borrow payloads before signing", async () => { + const action = borrowAction(); + const key = new BorrowTransactionWorkflowInput({ + action, + walletScope: borrowWalletScope, + }); + const wrongWallet = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key, + operations: makeOperations({ + getWalletState: Effect.succeed(walletState("base", otherAddress)), + }), + }); + const failed = yield* waitForState( + machine, + (state) => state._tag === "SignFailed" + ); + return Option.getOrThrow(yield* Fiber.join(failed)); + }) + ) + ); + expect(wrongWallet).toMatchObject({ + _tag: "SignFailed", + error: { _tag: "TransactionSignError", transactionId: "borrow-1" }, + }); + + const invalidAction = borrowAction({ + id: "borrow-invalid", + transactions: [ + borrowTransaction("invalid", { signablePayload: "not-json" }), + ], + }); + const invalidPayload = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: new BorrowTransactionWorkflowInput({ + action: invalidAction, + walletScope: borrowWalletScope, + }), + operations: makeOperations({ + getWalletState: Effect.succeed(walletState("base")), + }), + }); + const failed = yield* waitForState( + machine, + (state) => state._tag === "SignFailed" + ); + return Option.getOrThrow(yield* Fiber.join(failed)); + }) + ) + ); + expect(invalidPayload).toMatchObject({ + _tag: "SignFailed", + error: { + _tag: "TransactionSignError", + message: "Borrow transaction payload could not be decoded.", + }, + }); + }); + + it("normalizes borrow signed and broadcast submissions", async () => { + const confirmed = (action: Action) => + borrowAction({ + id: action.id, + status: "SUCCESS", + transactions: action.transactions.map((transaction) => + borrowTransaction(transaction.id, { status: "CONFIRMED" }) + ), + }); + + for (const broadcasted of [false, true]) { + const action = borrowAction({ id: `borrow-${broadcasted}` }); + const submit = vi.fn(() => + Effect.succeed({ + link: "https://explorer.test/borrow", + status: "BROADCASTED" as const, + transactionHash, + }) + ); + const result = await runToCompletion( + new BorrowTransactionWorkflowInput({ + action, + walletScope: borrowWalletScope, + }), + makeOperations({ + getBorrowAction: () => Effect.succeed(confirmed(action)), + getWalletState: Effect.succeed(walletState("base")), + signTransaction: () => + Effect.succeed({ + broadcasted, + signedTx: broadcasted ? transactionHash : signedPayload, + }) as never, + submitBorrowTransaction: submit, + }) + ); + const submission = result.finalState.context.submissions[0]; + + expect(submit).toHaveBeenCalledWith({ + command: broadcasted + ? { transactionHash } + : { signedPayload: signedPayload }, + transactionId: "borrow-1", + }); + expect(submission).toMatchObject({ + hash: transactionHash, + link: "https://explorer.test/borrow", + signedPayload: broadcasted ? null : signedPayload, + }); + } + }); + + it("polls on schedule and interrupts polling with its scope", async () => { + let checks = 0; + const firstCheck = await Effect.runPromise(Deferred.make()); + const operations = makeOperations({ + getClassicStatus: () => + Effect.gen(function* () { + checks += 1; + yield* Deferred.succeed(firstCheck, undefined); + return { explorerUrl: null, status: "PENDING" as const }; + }), + }); + + await Effect.runPromise( + Effect.gen(function* () { + yield* Effect.scoped( + Effect.gen(function* () { + yield* makeWorkflowFromService({ + key: new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("pending")], + walletScope: classicWalletScope, + yieldId, + }), + operations, + }); + yield* Deferred.await(firstCheck); + }) + ); + + yield* TestClock.adjust("1 minute"); + yield* Effect.yieldNow; + }).pipe(Effect.provide(TestClock.layer())) + ); + + expect(checks).toBe(1); + }); + + it("uses the classic confirmation interval and exhausts its attempt limit", async () => { + let checks = 0; + const firstCheck = await Effect.runPromise(Deferred.make()); + const failed = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: new ClassicTransactionWorkflowInput({ + actionMeta, + transactions: [classicTransaction("exhausted")], + walletScope: classicWalletScope, + yieldId, + }), + operations: makeOperations({ + getClassicStatus: () => + Effect.gen(function* () { + checks += 1; + if (checks === 1) { + yield* Deferred.succeed(firstCheck, undefined); + } + + return { explorerUrl: null, status: "PENDING" as const }; + }), + }), + }); + const failedState = yield* waitForState( + machine, + (state) => state._tag === "ConfirmationFailed" + ); + yield* Deferred.await(firstCheck); + expect(checks).toBe(1); + yield* TestClock.adjust("3999 millis"); + expect(checks).toBe(1); + yield* TestClock.adjust("1 millis"); + expect(checks).toBe(2); + yield* TestClock.adjust("5 minutes"); + + return Option.getOrThrow(yield* Fiber.join(failedState)); + }).pipe(Effect.provide(TestClock.layer())) + ) + ); + + expect(checks).toBe(75); + expect(failed).toMatchObject({ + _tag: "ConfirmationFailed", + error: { _tag: "TransactionConfirmationError" }, + }); + }); + + it("processes multiple borrow transactions in one batch", async () => { + const first = borrowAction({ + transactions: [ + borrowTransaction("borrow-1"), + borrowTransaction("borrow-2"), + ], + }); + const firstConfirmed = borrowAction({ + transactions: [ + borrowTransaction("borrow-1", { status: "CONFIRMED" }), + borrowTransaction("borrow-2"), + ], + }); + const completed = borrowAction({ + status: "SUCCESS", + transactions: [ + borrowTransaction("borrow-1", { status: "CONFIRMED" }), + borrowTransaction("borrow-2", { status: "CONFIRMED" }), + ], + }); + let checks = 0; + const result = await runToCompletion( + new BorrowTransactionWorkflowInput({ + action: first, + walletScope: borrowWalletScope, + }), + makeOperations({ + getBorrowAction: () => { + checks += 1; + return Effect.succeed(checks === 1 ? firstConfirmed : completed); + }, + getWalletState: Effect.succeed(walletState("base")), + }) + ); + + expect(result.finalState.context.batches).toHaveLength(1); + expect(result.finalState.context.submissions).toHaveLength(2); + expect( + result.finalState.context.batches[0]?.transactions.every( + ({ meta }) => meta.done + ) + ).toBe(true); + }); + + it("preserves history and event order across borrow transaction batches", async () => { + const first = borrowAction({ + hasNextStep: true, + totalSteps: 2, + transactions: [borrowTransaction("borrow-1")], + }); + const firstConfirmed = borrowAction({ + hasNextStep: true, + totalSteps: 2, + transactions: [borrowTransaction("borrow-1", { status: "CONFIRMED" })], + }); + const second = borrowAction({ + currentStep: 2, + id: first.id, + totalSteps: 2, + transactions: [borrowTransaction("borrow-2")], + }); + const secondConfirmed = borrowAction({ + currentStep: 2, + id: first.id, + status: "SUCCESS", + totalSteps: 2, + transactions: [borrowTransaction("borrow-2", { status: "CONFIRMED" })], + }); + let checks = 0; + const result = await runToCompletion( + new BorrowTransactionWorkflowInput({ + action: first, + walletScope: borrowWalletScope, + }), + makeOperations({ + getBorrowAction: () => { + checks += 1; + return Effect.succeed( + checks === 1 ? firstConfirmed : secondConfirmed + ); + }, + getWalletState: Effect.succeed(walletState("base")), + stepBorrowAction: () => Effect.succeed(second), + }) + ); + + expect(result.finalState.context.batches.map(({ id }) => id)).toEqual([ + "borrow-step-1", + "borrow-step-2", + ]); + expect(result.finalState.context.submissions).toHaveLength(2); + expect(result.events.map(({ _tag }) => _tag)).toEqual([ + "TransactionWorkflowSigned", + "TransactionWorkflowSubmitted", + "TransactionWorkflowBatchAdvanced", + "TransactionWorkflowSigned", + "TransactionWorkflowSubmitted", + "TransactionWorkflowCompleted", + ]); + }); + + it("reconciles an ambiguous failed borrow advancement before retrying", async () => { + const first = borrowAction({ hasNextStep: true, totalSteps: 2 }); + const firstConfirmed = borrowAction({ + hasNextStep: true, + totalSteps: 2, + transactions: [borrowTransaction("borrow-1", { status: "CONFIRMED" })], + }); + const second = borrowAction({ + currentStep: 2, + id: first.id, + status: "SUCCESS", + totalSteps: 2, + transactions: [], + }); + let statusChecks = 0; + const step = vi.fn(() => Effect.fail(new Error("response lost"))); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: new BorrowTransactionWorkflowInput({ + action: first, + walletScope: borrowWalletScope, + }), + operations: makeOperations({ + getBorrowAction: () => { + statusChecks += 1; + return Effect.succeed( + statusChecks === 1 ? firstConfirmed : second + ); + }, + getWalletState: Effect.succeed(walletState("base")), + stepBorrowAction: step, + }), + }); + const failed = yield* waitForState( + machine, + (state) => state._tag === "AdvanceFailed" + ); + yield* Fiber.join(failed); + const completed = yield* waitForState( + machine, + (state) => state._tag === "Completed" + ); + yield* machine.dispatch({ _tag: "Retry" }); + yield* Fiber.join(completed); + }) + ) + ); + + expect(step).toHaveBeenCalledTimes(1); + }); + + it("uses a reconciled next batch without repeating an ambiguous borrow step", async () => { + const first = borrowAction({ hasNextStep: true, totalSteps: 2 }); + const firstConfirmed = borrowAction({ + hasNextStep: true, + totalSteps: 2, + transactions: [borrowTransaction("borrow-1", { status: "CONFIRMED" })], + }); + const second = borrowAction({ + currentStep: 2, + id: first.id, + totalSteps: 2, + transactions: [borrowTransaction("borrow-2")], + }); + const secondCompleted = borrowAction({ + currentStep: 2, + id: first.id, + status: "SUCCESS", + totalSteps: 2, + transactions: [borrowTransaction("borrow-2", { status: "CONFIRMED" })], + }); + let statusChecks = 0; + const step = vi.fn(() => Effect.fail(new Error("response lost"))); + + const result = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const machine = yield* makeWorkflowFromService({ + key: new BorrowTransactionWorkflowInput({ + action: first, + walletScope: borrowWalletScope, + }), + operations: makeOperations({ + getBorrowAction: () => { + statusChecks += 1; + if (statusChecks === 1) return Effect.succeed(firstConfirmed); + if (statusChecks === 2) return Effect.succeed(second); + return Effect.succeed(secondCompleted); + }, + getWalletState: Effect.succeed(walletState("base")), + stepBorrowAction: step, + }), + }); + const failed = yield* waitForState( + machine, + (state) => state._tag === "AdvanceFailed" + ); + yield* Fiber.join(failed); + const completed = yield* waitForState( + machine, + (state) => state._tag === "Completed" + ); + yield* machine.dispatch({ _tag: "Retry" }); + + return Option.getOrThrow(yield* Fiber.join(completed)); + }) + ) + ); + + expect(step).toHaveBeenCalledTimes(1); + expect(result.context.batches.map(({ id }) => id)).toEqual([ + "borrow-step-1", + "borrow-step-2", + ]); + }); +}); diff --git a/packages/widget/tests/providers/api-client.dom.test.tsx b/packages/widget/tests/providers/api-client.dom.test.tsx new file mode 100644 index 000000000..7ce3d33a3 --- /dev/null +++ b/packages/widget/tests/providers/api-client.dom.test.tsx @@ -0,0 +1,266 @@ +import { Context, Effect, Layer, Stream, SubscriptionRef } from "effect"; +import { HttpResponse, http } from "msw"; +import { version as widgetVersion } from "../../package.json"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { useGeoBlock } from "../../src/features/preferences/react/use-geo-block"; +import { BorrowOperations } from "../../src/services/api/borrow-operations"; +import { BorrowResourceSource } from "../../src/services/api/borrow-resource-source"; +import { delayAPIRequests } from "../../src/services/api/delay-api-requests"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { ApiTransportService } from "../../src/services/api/transport"; +import { YieldOperations } from "../../src/services/api/yield-operations"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { + type WidgetApiConfig, + WidgetConfigService, +} from "../../src/services/config/widget-config"; +import { RichErrorService } from "../../src/services/errors/rich-error-service"; +import { config } from "../../src/shared/config/widget-defaults"; +import { describe, expect, it } from "../utils/test-extend.dom"; +import { renderHook } from "../utils/test-utils.dom"; + +const createTestClient = async (options: Partial = {}) => { + const config = normalizeWidgetConfig({ + apiKey: "test-key", + baseUrl: "https://api.example.com", + borrowApiUrl: "https://borrow.example.com", + yieldsApiUrl: "https://yield.example.com", + variant: "default", + ...options, + }); + const configLayer = WidgetConfigService.layer({ + initial: config, + changes: Stream.never, + current: Effect.succeed(config), + }); + const richErrorLayer = RichErrorService.layer.pipe( + Layer.provide(configLayer) + ); + const transportLayer = ApiTransportService.layer.pipe( + Layer.provide(richErrorLayer), + Layer.provide(configLayer) + ); + const clientLayer = Layer.mergeAll( + BorrowOperations.layer, + BorrowResourceSource.layer, + LegacyResourceSource.layer, + YieldOperations.layer, + YieldResourceSource.layer + ).pipe(Layer.provide(transportLayer)); + const context = await Effect.runPromise( + Layer.build(Layer.merge(clientLayer, richErrorLayer)).pipe(Effect.scoped) + ); + + return { + client: { + borrowOperations: Context.get(context, BorrowOperations), + borrowSource: Context.get(context, BorrowResourceSource), + legacySource: Context.get(context, LegacyResourceSource), + yieldOperations: Context.get(context, YieldOperations), + yieldSource: Context.get(context, YieldResourceSource), + }, + richErrors: Context.get(context, RichErrorService), + }; +}; + +const normalizeUrl = (url: string) => url.replace(/\/$/, ""); + +describe("Effect API client", () => { + it("constructs all typed clients with shared headers", async ({ worker }) => { + const calls: Array<{ headers: Headers; url: string }> = []; + worker.use( + http.get("https://api.example.com/v1/tokens", ({ request }) => { + calls.push({ headers: request.headers, url: request.url }); + return HttpResponse.json([]); + }), + http.get("https://yield.example.com/health", ({ request }) => { + calls.push({ headers: request.headers, url: request.url }); + return HttpResponse.json({ + status: "OK", + timestamp: "1970-01-01T00:00:00.000Z", + }); + }), + http.get("https://borrow.example.com/v1/integrations", ({ request }) => { + calls.push({ headers: request.headers, url: request.url }); + return HttpResponse.json([]); + }) + ); + const { client } = await createTestClient(); + + await Effect.runPromise(client.legacySource.getTokenOptions()); + await Effect.runPromise(client.yieldSource.getHealth()); + await Effect.runPromise(client.borrowSource.getIntegrations()); + + expect(calls.map((call) => call.url)).toEqual([ + "https://api.example.com/v1/tokens", + "https://yield.example.com/health", + "https://borrow.example.com/v1/integrations", + ]); + expect( + calls.every((call) => call.headers.get("X-API-KEY") === "test-key") + ).toBe(true); + expect( + calls.every( + (call) => call.headers.get("X-Yield-Widget-Version") === widgetVersion + ) + ).toBe(true); + }); + + it("keeps Borrow operations unavailable when configuration is missing", async () => { + const { client } = await createTestClient({ borrowApiUrl: " " }); + await expect( + Effect.runPromise(client.borrowSource.getIntegrations()) + ).rejects.toBeTruthy(); + }); + + it("records rich errors and geo-block responses", async ({ worker }) => { + const geoBlock = await renderHook(() => useGeoBlock()); + const apiUrl = normalizeUrl(config.env.apiUrl); + let response: "rich" | "geo" = "rich"; + worker.use( + http.get(`${apiUrl}/v1/tokens`, () => + response === "rich" + ? HttpResponse.json( + { + code: 400, + details: { code: "TEST" }, + message: "Rich failure", + }, + { status: 400 } + ) + : HttpResponse.json( + { + countryCode: "CA", + message: "Access denied", + regionCode: "CA-ON", + tags: ["staking"], + type: "GEO_LOCATION", + }, + { status: 403 } + ) + ) + ); + const { client, richErrors } = await createTestClient({ baseUrl: apiUrl }); + + try { + await geoBlock.act(async () => { + await expect( + Effect.runPromise(client.legacySource.getTokenOptions()) + ).rejects.toBeTruthy(); + await expect + .poll(() => + Effect.runPromise(SubscriptionRef.get(richErrors.current)).then( + (error) => error?.message + ) + ) + .toBe("Rich failure"); + + response = "geo"; + await expect( + Effect.runPromise(client.legacySource.getTokenOptions()) + ).rejects.toBeTruthy(); + await expect + .poll(() => { + const value = geoBlock.result.current; + return value === false ? undefined : value.countryCode; + }) + .toBe("CA"); + }); + } finally { + await geoBlock.act(() => Effect.runPromise(richErrors.reset)); + geoBlock.unmount(); + } + }); + + it("retries transient response statuses for every API", async ({ + worker, + }) => { + const transientAttempts = { + borrow: 0, + legacy: 0, + yield: 0, + }; + let badRequestAttempts = 0; + worker.use( + http.get("https://api.example.com/v1/tokens", () => { + transientAttempts.legacy += 1; + return transientAttempts.legacy < 3 + ? HttpResponse.json( + { code: 500, message: "temporary" }, + { status: 500 } + ) + : HttpResponse.json([]); + }), + http.get("https://yield.example.com/health", () => { + transientAttempts.yield += 1; + return transientAttempts.yield < 3 + ? HttpResponse.json( + { code: 500, message: "temporary" }, + { status: 500 } + ) + : HttpResponse.json({ + status: "OK", + timestamp: "1970-01-01T00:00:00.000Z", + }); + }), + http.get("https://borrow.example.com/v1/integrations", () => { + transientAttempts.borrow += 1; + return transientAttempts.borrow < 3 + ? HttpResponse.json( + { code: 500, message: "temporary" }, + { status: 500 } + ) + : HttpResponse.json([]); + }) + ); + const { client } = await createTestClient(); + + await Effect.runPromise(client.legacySource.getTokenOptions()); + await Effect.runPromise(client.yieldSource.getHealth()); + await Effect.runPromise(client.borrowSource.getIntegrations()); + expect(transientAttempts).toEqual({ borrow: 3, legacy: 3, yield: 3 }); + + worker.use( + http.get("https://api.example.com/v1/tokens", () => { + badRequestAttempts += 1; + return HttpResponse.json( + { code: 400, message: "bad request" }, + { status: 400 } + ); + }) + ); + await expect( + Effect.runPromise(client.legacySource.getTokenOptions()) + ).rejects.toBeTruthy(); + expect(badRequestAttempts).toBe(1); + }); + + it("waits for delayed API requests before resolving", async ({ worker }) => { + const env = config.env as unknown as { isTestMode: boolean }; + const originalIsTestMode = env.isTestMode; + env.isTestMode = false; + const releaseDelay = delayAPIRequests(); + let resolved = false; + worker.use( + http.get("https://api.example.com/v1/tokens", () => HttpResponse.json([])) + ); + const { client } = await createTestClient(); + + try { + const request = Effect.runPromise( + client.legacySource.getTokenOptions() + ).then(() => { + resolved = true; + }); + + await Promise.resolve(); + expect(resolved).toBe(false); + releaseDelay(); + await request; + expect(resolved).toBe(true); + } finally { + releaseDelay(); + env.isTestMode = originalIsTestMode; + } + }); +}); diff --git a/packages/widget/tests/providers/api-client.test.tsx b/packages/widget/tests/providers/api-client.test.tsx deleted file mode 100644 index e2ee772eb..000000000 --- a/packages/widget/tests/providers/api-client.test.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { HttpResponse, http } from "msw"; -import { delayAPIRequests } from "../../src/common/delay-api-requests"; -import { config } from "../../src/config"; -import { useGeoBlock } from "../../src/hooks/use-geo-block"; -import { useRichErrors } from "../../src/hooks/use-rich-errors"; -import { createApiClient } from "../../src/providers/api/api-client"; -import { describe, expect, it } from "../utils/test-extend"; -import { renderHook } from "../utils/test-utils"; - -const createTestClient = ( - options: Partial[0]> = {} -) => - createApiClient({ - apiKey: "test-key", - baseUrl: "https://api.example.com", - yieldsApiUrl: "https://yield.example.com", - ...options, - }); - -const normalizeUrl = (url: string) => url.replace(/\/$/, ""); - -describe("API client", () => { - it("constructs bound generated legacy and Yield clients with shared headers", async ({ - worker, - }) => { - const calls: Array<{ headers: Headers; url: string }> = []; - worker.use( - http.get("https://api.example.com/v1/tokens", ({ request }) => { - calls.push({ headers: request.headers, url: request.url }); - - return HttpResponse.json([]); - }), - http.get("https://yield.example.com/health", ({ request }) => { - calls.push({ headers: request.headers, url: request.url }); - - return HttpResponse.json({ - status: "OK", - timestamp: new Date(0).toISOString(), - }); - }) - ); - const client = createTestClient(); - - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).resolves.toEqual([]); - await expect( - client.yield.HealthControllerHealth(undefined) - ).resolves.toMatchObject({ - status: "OK", - }); - - expect(calls.map((call) => call.url)).toEqual([ - "https://api.example.com/v1/tokens", - "https://yield.example.com/health", - ]); - expect( - calls.every((call) => call.headers.get("X-API-KEY") === "test-key") - ).toBe(true); - }); - - it("exposes only the generated operations currently used by the app", () => { - const client = createTestClient(); - - expect("TokenControllerGetTokens" in client.legacy).toBe(true); - expect("AuthControllerMe" in client.legacy).toBe(false); - expect("TokensControllerGetTokens" in client.yield).toBe(true); - expect("YieldsControllerGetAggregateBalances" in client.yield).toBe(true); - expect("ProvidersControllerGetProvider" in client.yield).toBe(true); - expect("ProvidersControllerGetProviders" in client.yield).toBe(false); - }); - - it("records rich errors for failed StakeKit API responses", async ({ - worker, - }) => { - const richError = await renderHook(() => useRichErrors()); - richError.result.current.resetError(); - const apiUrl = normalizeUrl(config.env.apiUrl); - worker.use( - http.get(`${apiUrl}/v1/tokens`, () => - HttpResponse.json( - { code: 400, details: { code: "TEST" }, message: "Rich failure" }, - { status: 400 } - ) - ) - ); - const client = createTestClient({ baseUrl: apiUrl }); - - try { - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).rejects.toMatchObject({ - _tag: "TokenControllerGetTokens400", - response: { status: 400 }, - }); - await expect - .poll(() => richError.result.current.error?.message) - .toBe("Rich failure"); - } finally { - richError.unmount(); - } - }); - - it("can suppress rich errors for optional API requests", async ({ - worker, - }) => { - const richError = await renderHook(() => useRichErrors()); - richError.result.current.resetError(); - const apiUrl = normalizeUrl(config.env.apiUrl); - worker.use( - http.get(`${apiUrl}/v1/tokens`, () => - HttpResponse.json( - { - code: 400, - details: { code: "TEST" }, - message: "Optional failure", - }, - { status: 400 } - ) - ) - ); - const client = createTestClient({ baseUrl: apiUrl }); - - try { - await expect( - client - .withOptions({ suppressRichErrors: true }) - .legacy.TokenControllerGetTokens(undefined) - ).rejects.toMatchObject({ - _tag: "TokenControllerGetTokens400", - response: { status: 400 }, - }); - await Promise.resolve(); - - expect(richError.result.current.error).toBeNull(); - } finally { - richError.unmount(); - } - }); - - it("records geo-block responses", async ({ worker }) => { - const geoBlock = await renderHook(() => useGeoBlock()); - const apiUrl = normalizeUrl(config.env.apiUrl); - worker.use( - http.get(`${apiUrl}/v1/tokens`, () => - HttpResponse.json( - { - countryCode: "CA", - message: "Access denied", - regionCode: "CA-ON", - tags: ["staking"], - type: "GEO_LOCATION", - }, - { status: 403 } - ) - ) - ); - const client = createTestClient({ baseUrl: apiUrl }); - - try { - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).rejects.toBeTruthy(); - await expect - .poll(() => { - const value = geoBlock.result.current; - - return value === false ? undefined : value.countryCode; - }) - .toBe("CA"); - - const value = geoBlock.result.current; - expect(value === false ? [] : [...value.tags]).toEqual(["staking"]); - } finally { - geoBlock.unmount(); - } - }); - - it("retries transient response statuses", async ({ worker }) => { - let attempts = 0; - worker.use( - http.get("https://api.example.com/v1/tokens", () => { - attempts += 1; - - return attempts < 3 - ? HttpResponse.json( - { code: 500, message: "temporary" }, - { status: 500 } - ) - : HttpResponse.json([]); - }) - ); - const client = createTestClient(); - - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).resolves.toEqual([]); - expect(attempts).toBe(3); - }); - - it("does not retry non-transient response statuses", async ({ worker }) => { - let attempts = 0; - worker.use( - http.get("https://api.example.com/v1/tokens", () => { - attempts += 1; - - return HttpResponse.json( - { code: 400, message: "bad request" }, - { status: 400 } - ); - }) - ); - const client = createTestClient(); - - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).rejects.toMatchObject({ - _tag: "TokenControllerGetTokens400", - cause: { code: 400, message: "bad request" }, - response: { status: 400 }, - }); - expect(attempts).toBe(1); - }); - - it("does not retry aborted requests", async ({ worker }) => { - let attempts = 0; - const controller = new AbortController(); - worker.use( - http.get("https://api.example.com/v1/tokens", async ({ request }) => { - attempts += 1; - controller.abort(); - - await new Promise((resolve) => { - request.signal.addEventListener("abort", resolve, { once: true }); - }); - - return HttpResponse.json([]); - }) - ); - const client = createTestClient(); - - await expect( - client - .withOptions({ signal: controller.signal }) - .legacy.TokenControllerGetTokens(undefined) - ).rejects.toBeTruthy(); - expect(attempts).toBeLessThanOrEqual(1); - }); - - it("waits for delayed API requests before resolving successful responses", async ({ - worker, - }) => { - const env = config.env as unknown as { isTestMode: boolean }; - const originalIsTestMode = env.isTestMode; - env.isTestMode = false; - - const releaseDelay = delayAPIRequests(); - let resolved = false; - worker.use( - http.get("https://api.example.com/v1/tokens", () => HttpResponse.json([])) - ); - const client = createTestClient(); - - try { - const request = client.legacy - .TokenControllerGetTokens(undefined) - .then(() => { - resolved = true; - }); - - await Promise.resolve(); - await Promise.resolve(); - - expect(resolved).toBe(false); - - releaseDelay(); - await request; - - expect(resolved).toBe(true); - } finally { - releaseDelay(); - env.isTestMode = originalIsTestMode; - } - }); -}); diff --git a/packages/widget/tests/providers/api-service.test.ts b/packages/widget/tests/providers/api-service.test.ts new file mode 100644 index 000000000..91ae29b1b --- /dev/null +++ b/packages/widget/tests/providers/api-service.test.ts @@ -0,0 +1,534 @@ +import { Context, Effect, Layer, Option, Schema } from "effect"; +import * as HttpClientError from "effect/unstable/http/HttpClientError"; +import { describe, expect, it, vi } from "vitest"; +import { YieldAction } from "../../src/domain/schema/action-models"; +import { + ApiRequestError, + MissingBorrowApiConfig, + ResponseDecodeError, +} from "../../src/domain/schema/api-errors"; +import { RewardsAddresses } from "../../src/domain/schema/dashboard-models"; +import { WalletAddress, YieldId } from "../../src/domain/schema/identifiers"; +import { + BorrowOperations, + makeBorrowOperations, +} from "../../src/services/api/borrow-operations"; +import { + BorrowResourceSource, + makeBorrowResourceSource, +} from "../../src/services/api/borrow-resource-source"; +import { + LegacyResourceSource, + makeLegacyResourceSource, +} from "../../src/services/api/legacy-resource-source"; +import { + makeYieldOperations, + YieldOperations, +} from "../../src/services/api/yield-operations"; +import { + makeYieldResourceSource, + YieldResourceSource, +} from "../../src/services/api/yield-resource-source"; +import { + yieldApiActionDtoFixture, + yieldApiProviderFixture, + yieldApiYieldDtoFixture, + yieldApiYieldFixture, +} from "../fixtures"; +import { makeTestStakeKitApiLayer } from "../utils/stakekit-api-layer"; + +const config = { + apiKey: "test-key", + baseUrl: "https://api.example.com", + borrowApiUrl: "https://borrow.example.com", + yieldsApiUrl: "https://yield.example.com", +}; + +const address = Schema.decodeUnknownSync(WalletAddress)("0xWallet"); +const firstYieldId = Schema.decodeUnknownSync(YieldId)( + "ethereum-eth-native-staking" +); +const secondYieldId = Schema.decodeUnknownSync(YieldId)( + "cosmos-atom-native-staking" +); + +describe("application API services", () => { + it("constructs separated Legacy, Yield, and Borrow operation surfaces", async () => { + const context = await Effect.runPromise( + Layer.build(makeTestStakeKitApiLayer(config)).pipe(Effect.scoped) + ); + const borrowOperations = Context.get(context, BorrowOperations); + const borrowSource = Context.get(context, BorrowResourceSource); + const legacySource = Context.get(context, LegacyResourceSource); + const yieldOperations = Context.get(context, YieldOperations); + const yieldSource = Context.get(context, YieldResourceSource); + + expect(legacySource.getEnabledNetworks).toBeTypeOf("function"); + expect(legacySource.scanTokenBalances).toBeTypeOf("function"); + expect(legacySource.getPrices).toBeTypeOf("function"); + expect(legacySource.getRewardsSummaries).toBeTypeOf("function"); + expect(yieldOperations.previewAction).toBeTypeOf("function"); + expect(yieldSource.getPositions).toBeTypeOf("function"); + expect(yieldSource.getHealth).toBeTypeOf("function"); + expect(yieldSource.getOpportunity).toBeTypeOf("function"); + expect(yieldSource.getProvider).toBeTypeOf("function"); + expect(yieldSource.listYields).toBeTypeOf("function"); + expect(borrowOperations.executeAction).toBeTypeOf("function"); + expect(borrowSource.getMarkets).toBeTypeOf("function"); + }); + + it("fails only Borrow capabilities when Borrow configuration is missing", async () => { + const context = await Effect.runPromise( + Layer.build( + makeTestStakeKitApiLayer({ ...config, borrowApiUrl: " " }) + ).pipe(Effect.scoped) + ); + const operations = Context.get(context, BorrowOperations); + const source = Context.get(context, BorrowResourceSource); + + await expect( + Effect.runPromise(source.getIntegrations()) + ).rejects.toBeInstanceOf(MissingBorrowApiConfig); + await expect( + Effect.runPromise(operations.getAction("borrow-action")) + ).rejects.toBeInstanceOf(MissingBorrowApiConfig); + }); + + it("maps Borrow market reads through the narrow source capability", async () => { + const markets = vi.fn(() => + Effect.succeed({ items: [], limit: 100, offset: 0, total: 0 }) + ); + const source = makeBorrowResourceSource({ + MarketsControllerGetMarketsV1: markets, + } as never); + const request = { + limit: 100, + network: "ethereum" as const, + offset: 0, + scope: "all" as const, + }; + + expect(await Effect.runPromise(source.getMarkets(request))).toEqual({ + items: [], + limit: 100, + offset: 0, + total: 0, + }); + expect(markets).toHaveBeenCalledWith({ params: request }); + }); + + it("maps Borrow action polling through the narrow operation capability", async () => { + const getAction = vi.fn(() => Effect.succeed(null)); + const operations = makeBorrowOperations({ + ActionsControllerGetActionV1: getAction, + } as never); + + expect(await Effect.runPromise(operations.getAction("borrow-action"))).toBe( + null + ); + expect(getAction).toHaveBeenCalledWith("borrow-action", undefined); + }); + + it("returns decoded domain values from successful transport responses", async () => { + const source = makeYieldResourceSource({ + KycControllerGetStatus: () => Effect.succeed({ kycStatus: "approved" }), + } as never); + + const result = await Effect.runPromise( + source.getKycStatus({ address, yieldId: firstYieldId }) + ); + + expect(result).toEqual({ kycStatus: "approved" }); + }); + + it("maps Health through the Yield read capability", async () => { + const health = vi.fn(() => + Effect.succeed({ + status: "OK", + timestamp: "1970-01-01T00:00:00.000Z", + }) + ); + const source = makeYieldResourceSource({ + HealthControllerHealth: health, + } as never); + + expect((await Effect.runPromise(source.getHealth())).status).toBe("OK"); + expect(health).toHaveBeenCalledWith(undefined); + }); + + it("maps enabled networks through the Legacy read capability", async () => { + const enabledNetworks = vi.fn(() => Effect.succeed(["ethereum", "solana"])); + const source = makeLegacyResourceSource({ + YieldControllerGetMyNetworks: enabledNetworks, + } as never); + + expect(await Effect.runPromise(source.getEnabledNetworks())).toEqual( + new Set(["ethereum", "solana"]) + ); + expect(enabledNetworks).toHaveBeenCalledWith(undefined); + }); + + it("maps Action Preview through the narrow Yield operation capability", async () => { + const action = yieldApiActionDtoFixture(); + const expected = Schema.decodeUnknownSync(YieldAction)(action); + const enter = vi.fn(() => Effect.succeed(action)); + const operations = makeYieldOperations({ + ActionsControllerEnterYield: enter, + } as never); + const command = { + address, + arguments: { amount: "1" }, + yieldId: firstYieldId, + }; + + expect( + await Effect.runPromise( + operations.previewAction({ command, intent: "enter" }) + ) + ).toEqual(expected); + expect(enter).toHaveBeenCalledWith({ payload: command }); + }); + + it("maps Yield position requests through the narrow read capability", async () => { + const aggregateBalances = vi.fn(() => + Effect.succeed({ errors: [], items: [] }) + ); + const source = makeYieldResourceSource({ + YieldsControllerGetAggregateBalances: aggregateBalances, + } as never); + const command = { + queries: [{ address, network: "ethereum" as const }], + }; + + const result = await Effect.runPromise(source.getPositions(command)); + + expect(result).toEqual({ errors: [], items: [] }); + expect(aggregateBalances).toHaveBeenCalledWith({ payload: command }); + }); + + it("maps Yield directory filters through the narrow read capability", async () => { + const list = vi.fn(() => + Effect.succeed({ items: [], limit: 100, offset: 0, total: 0 }) + ); + const source = makeYieldResourceSource({ + YieldsControllerGetYields: list, + } as never); + const request = { + limit: 100, + network: "ethereum" as const, + offset: 0, + types: ["staking" as const], + yieldIds: [firstYieldId], + }; + + expect(await Effect.runPromise(source.listYields(request))).toEqual({ + items: [], + limit: 100, + offset: 0, + total: 0, + }); + expect(list).toHaveBeenCalledWith({ params: request }); + }); + + it("maps Yield and Legacy token discovery through narrow read capabilities", async () => { + const yieldTokens = vi.fn(() => + Effect.succeed({ items: [], limit: 100, offset: 0, total: 0 }) + ); + const legacyTokens = vi.fn(() => Effect.succeed([])); + const yieldSource = makeYieldResourceSource({ + TokensControllerGetTokens: yieldTokens, + } as never); + const legacySource = makeLegacyResourceSource({ + TokenControllerGetTokens: legacyTokens, + } as never); + + await Effect.runPromise( + yieldSource.listYieldTokens({ + limit: 100, + networks: ["ethereum"], + offset: 0, + yieldTypes: ["staking"], + }) + ); + await Effect.runPromise(legacySource.getTokenOptions("ethereum")); + + expect(yieldTokens).toHaveBeenCalledWith({ + params: { + limit: 100, + networks: ["ethereum"], + offset: 0, + yieldTypes: ["staking"], + }, + }); + expect(legacyTokens).toHaveBeenCalledWith({ + params: { network: "ethereum" }, + }); + }); + + it("maps complete validator query identity through the Yield read capability", async () => { + const validators = vi.fn(() => + Effect.succeed({ items: [], limit: 100, offset: 0, total: 0 }) + ); + const source = makeYieldResourceSource({ + YieldsControllerGetYieldValidators: validators, + } as never); + const request = { + address: "validator-address", + limit: 100, + name: "validator-name", + offset: 0, + preferred: false, + status: "active" as const, + yieldId: firstYieldId, + }; + + await Effect.runPromise(source.listValidators(request)); + + expect(validators).toHaveBeenCalledWith(firstYieldId, { + params: { + address: "validator-address", + limit: 100, + name: "validator-name", + offset: 0, + preferred: false, + status: "active", + }, + }); + }); + + it("maps Activity history query identity through the Yield read capability", async () => { + const activity = vi.fn(() => + Effect.succeed({ items: [], limit: 50, offset: 0, total: 0 }) + ); + const source = makeYieldResourceSource({ + ActionsControllerGetActions: activity, + } as never); + const request = { + address, + limit: 50, + network: "ethereum" as const, + offset: 0, + statuses: ["FAILED" as const, "SUCCESS" as const], + }; + + await Effect.runPromise(source.listActivity(request)); + + expect(activity).toHaveBeenCalledWith({ params: request }); + }); + + it("maps single-Yield and gas balances through narrow read capabilities", async () => { + const singleBalances = vi.fn(() => + Effect.succeed({ balances: [], yieldId: firstYieldId }) + ); + const gasBalances = vi.fn(() => Effect.succeed([])); + const yieldSource = makeYieldResourceSource({ + YieldsControllerGetYieldBalances: singleBalances, + } as never); + const legacySource = makeLegacyResourceSource({ + TokenControllerGetTokenBalances: gasBalances, + } as never); + const gasCommand = { + addresses: [{ address, network: "ethereum" as const }], + }; + + await Effect.runPromise( + yieldSource.getSingleYieldBalances({ + address, + yieldId: firstYieldId, + }) + ); + await Effect.runPromise(legacySource.getGasTokenBalances(gasCommand)); + + expect(singleBalances).toHaveBeenCalledWith(firstYieldId, { + payload: { address }, + }); + expect(gasBalances).toHaveBeenCalledWith({ payload: gasCommand }); + }); + + it("maps price requests through the Legacy read capability", async () => { + const prices = vi.fn(() => Effect.succeed({})); + const source = makeLegacyResourceSource({ + TokenControllerGetTokenPrices: prices, + } as never); + const token = yieldApiYieldFixture().token; + const request = { currency: "USD", tokenList: [token] }; + + await Effect.runPromise(source.getPrices(request)); + + expect(prices).toHaveBeenCalledWith({ payload: request }); + }); + + it("maps Yield history identity through the Yield read capability", async () => { + const rewardHistory = vi.fn(() => Effect.succeed({})); + const tvlHistory = vi.fn(() => Effect.succeed({})); + const source = makeYieldResourceSource({ + YieldsControllerGetYieldRewardRateHistory: rewardHistory, + YieldsControllerGetYieldTvlHistory: tvlHistory, + } as never); + const request = { + interval: "week" as const, + period: "1y" as const, + yieldId: firstYieldId, + }; + + await Effect.runPromise( + source.getRewardRateHistory(request).pipe(Effect.flip) + ); + await Effect.runPromise(source.getTvlHistory(request).pipe(Effect.flip)); + + expect(rewardHistory).toHaveBeenCalledWith(firstYieldId, { + params: { interval: "week", period: "1y" }, + }); + expect(tvlHistory).toHaveBeenCalledWith(firstYieldId, { + params: { interval: "week", period: "1y" }, + }); + }); + + it("maps opportunity and provider lookups through the narrow read capability", async () => { + const yieldDto = yieldApiYieldDtoFixture(); + const yieldModel = yieldApiYieldFixture(yieldDto); + const provider = yieldApiProviderFixture({ id: yieldModel.providerId }); + const getYield = vi.fn(() => Effect.succeed(yieldDto)); + const getProvider = vi.fn(() => Effect.succeed(provider)); + const source = makeYieldResourceSource({ + ProvidersControllerGetProvider: getProvider, + YieldsControllerGetYield: getYield, + } as never); + + expect( + await Effect.runPromise(source.getOpportunity(yieldModel.id)) + ).toEqual(yieldModel); + expect(await Effect.runPromise(source.getProvider(provider.id))).toEqual( + Option.some(provider) + ); + expect(getYield).toHaveBeenCalledWith(yieldModel.id, undefined); + expect(getProvider).toHaveBeenCalledWith(provider.id, undefined); + }); + + it("reports invalid consumed opportunity arguments as response decode errors", async () => { + const valid = yieldApiYieldDtoFixture(); + const getYield = vi.fn(() => + Effect.succeed({ + ...valid, + mechanics: { + ...valid.mechanics, + arguments: { + enter: { + fields: [ + { + label: "Amount", + minimum: "not-a-number", + name: "amount", + type: "string", + }, + ], + }, + }, + }, + }) + ); + const source = makeYieldResourceSource({ + YieldsControllerGetYield: getYield, + } as never); + + await expect( + Effect.runPromise(source.getOpportunity(firstYieldId)) + ).rejects.toBeInstanceOf(ResponseDecodeError); + }); + + it("maps a provider 404 to explicit absence at the source boundary", async () => { + const provider = yieldApiProviderFixture(); + const getProvider = vi.fn(() => + Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.StatusCodeError({ + request: {} as never, + response: { status: 404 } as never, + }), + }) + ) + ); + const source = makeYieldResourceSource({ + ProvidersControllerGetProvider: getProvider, + } as never); + + expect( + Option.isNone(await Effect.runPromise(source.getProvider(provider.id))) + ).toBe(true); + }); + + it("maps token balance scans through the narrow Legacy read capability", async () => { + const scan = vi.fn(() => Effect.succeed([])); + const source = makeLegacyResourceSource({ + TokenControllerTokenBalancesScan: scan, + } as never); + const command = { + addresses: { address }, + network: "ethereum" as const, + }; + + expect(await Effect.runPromise(source.scanTokenBalances(command))).toEqual( + [] + ); + expect(scan).toHaveBeenCalledWith({ payload: command }); + }); + + it("maps transport failures independently from response decoding failures", async () => { + const transportFailureSource = makeYieldResourceSource({ + KycControllerGetStatus: () => Effect.fail(new Error("offline")), + } as never); + const malformedResponseSource = makeYieldResourceSource({ + KycControllerGetStatus: () => Effect.succeed({ kycStatus: "unexpected" }), + } as never); + + const requestError = await Effect.runPromise( + transportFailureSource + .getKycStatus({ address, yieldId: firstYieldId }) + .pipe(Effect.flip) + ); + const decodeError = await Effect.runPromise( + malformedResponseSource + .getKycStatus({ address, yieldId: firstYieldId }) + .pipe(Effect.flip) + ); + + expect(requestError).toBeInstanceOf(ApiRequestError); + expect(requestError.operation).toBe("yield-kyc-status"); + expect(decodeError).toBeInstanceOf(ResponseDecodeError); + expect(decodeError.operation).toBe("yield-kyc-status"); + }); + + it("retains valid aggregate entries when a sibling response is malformed", async () => { + const rewards = { + last24H: "0", + last30D: "3", + last7D: "1", + lastYear: "12", + total: "20", + }; + const token = { + decimals: 18, + name: "Ethereum", + network: "ethereum", + symbol: "ETH", + }; + const source = makeLegacyResourceSource({ + YieldControllerGetSingleYieldRewardsSummary: (yieldId: string) => + Effect.succeed( + yieldId === firstYieldId + ? { rewards, token } + : { rewards, token: { ...token, decimals: "invalid" } } + ), + } as never); + const addresses = Schema.decodeUnknownSync(RewardsAddresses)({ address }); + + const result = await Effect.runPromise( + source.getRewardsSummaries({ + addresses, + yieldIds: [firstYieldId, secondYieldId], + }) + ); + + expect(Object.keys(result)).toEqual([firstYieldId]); + }); +}); diff --git a/packages/widget/tests/providers/entry-wallet-contracts.dom.test.ts b/packages/widget/tests/providers/entry-wallet-contracts.dom.test.ts new file mode 100644 index 000000000..713f54b71 --- /dev/null +++ b/packages/widget/tests/providers/entry-wallet-contracts.dom.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, expectTypeOf, it, vi } from "vitest"; +import { + renderSKWidget as bundledRenderSKWidget, + SKApp as PackageSKApp, + renderSKWidget, + SKApp, +} from "../../src/App"; +import type { + BundledSKWidgetProps, + SKWallet as BundledWallet, + SKAppProps as PackageSKAppProps, + SKWallet as PackageWallet, +} from "../../src/public-api/types"; + +const genericWallet: PackageWallet = { + signMessage: vi.fn(async () => "signed-message"), + switchChain: vi.fn(async () => undefined), + sendTransaction: vi.fn(async () => ({ + type: "success" as const, + txHash: "0xbroadcast-hash", + })), +}; + +const packageProps = { + apiKey: "public-contract-api-key", + externalProviders: { + type: "generic", + currentAddress: "0x0000000000000000000000000000000000000001", + currentChain: 1, + supportedChainIds: [1], + provider: genericWallet, + }, +} satisfies PackageSKAppProps; + +describe("package and bundled wallet entry contracts", () => { + it("exports the same component and bundled renderer implementations", () => { + expect(PackageSKApp).toBe(SKApp); + expect(bundledRenderSKWidget).toBe(renderSKWidget); + }); + + it("keeps generic wallet callbacks Promise-based in both entry modes", () => { + expectTypeOf().toEqualTypeOf(); + expectTypeOf(genericWallet.signMessage).returns.toEqualTypeOf< + Promise + >(); + expectTypeOf(genericWallet.switchChain).returns.toEqualTypeOf< + Promise + >(); + expectTypeOf(genericWallet.sendTransaction).returns.toMatchTypeOf< + Promise< + | string + | { type: "success"; txHash: string } + | { type: "error"; error: string } + > + >(); + expectTypeOf(packageProps).toMatchTypeOf(); + }); + + it("keeps bundled lifecycle controls aligned with package SKApp props", () => { + type BundledRenderProps = Parameters[0]; + type BundledController = ReturnType; + const bundledRenderProps = { + ...packageProps, + container: {} as HTMLElement, + } satisfies BundledRenderProps; + + expectTypeOf(packageProps).toMatchTypeOf(); + expectTypeOf(bundledRenderProps).toMatchTypeOf(); + expectTypeOf().toEqualTypeOf<{ + rerender: (newProps: PackageSKAppProps) => void; + unmount: () => void; + }>(); + }); +}); diff --git a/packages/widget/tests/providers/external-provider-contract.test.ts b/packages/widget/tests/providers/external-provider-contract.test.ts new file mode 100644 index 000000000..f953a82cb --- /dev/null +++ b/packages/widget/tests/providers/external-provider-contract.test.ts @@ -0,0 +1,104 @@ +import { Effect, Schema } from "effect"; +import type { RefObject } from "react"; +import { describe, expect, it, vi } from "vitest"; +import { ActionId, TransactionId } from "../../src/domain/schema/identifiers"; +import { + ExternalProvider, + ExternalProviderError, +} from "../../src/domain/types/external-providers"; +import type { + SKExternalProviders, + SKTx, + SKTxMeta, +} from "../../src/public-api/types"; + +const transaction = { + type: "solana", + tx: "01020304", +} satisfies SKTx; + +const transactionMeta = { + actionId: Schema.decodeSync(ActionId)("action-id"), + actionType: "STAKE", + amount: "1", + annotatedTransaction: null, + inputToken: undefined, + providersDetails: [], + structuredTransaction: null, + txId: Schema.decodeSync(TransactionId)("transaction-id"), + txType: "STAKE", +} satisfies SKTxMeta; + +const makeProviderRef = ( + provider: SKExternalProviders["provider"] +): RefObject => ({ + current: { + type: "generic", + currentAddress: "solana-address", + currentChain: 501, + supportedChainIds: [501], + provider, + }, +}); + +describe("generic external provider callback contract", () => { + it("passes message, chain, transaction, and metadata through Promise callbacks", async () => { + const signMessage = vi.fn(async () => "signed-message"); + const switchChain = vi.fn(async () => undefined); + const sendTransaction = vi.fn(async () => ({ + type: "success" as const, + txHash: "broadcast-hash", + })); + const provider = new ExternalProvider( + makeProviderRef({ signMessage, switchChain, sendTransaction }) + ); + + await expect( + Effect.runPromise(provider.signMessage("message-hash")) + ).resolves.toBe("signed-message"); + await expect( + Effect.runPromise(provider.switchChain({ chainId: 501 })) + ).resolves.toBeUndefined(); + await expect( + Effect.runPromise(provider.sendTransaction(transaction, transactionMeta)) + ).resolves.toBe("broadcast-hash"); + + expect(signMessage).toHaveBeenCalledWith("message-hash"); + expect(switchChain).toHaveBeenCalledWith(501); + expect(sendTransaction).toHaveBeenCalledWith(transaction, transactionMeta); + }); + + it("accepts legacy string hashes and preserves host error messages", async () => { + const stringProvider = new ExternalProvider( + makeProviderRef({ + signMessage: async () => "signed-message", + switchChain: async () => undefined, + sendTransaction: async () => "legacy-broadcast-hash", + }) + ); + const errorProvider = new ExternalProvider( + makeProviderRef({ + signMessage: async () => "signed-message", + switchChain: async () => undefined, + sendTransaction: async () => ({ + type: "error", + error: "Transaction blocked by host policy", + }), + }) + ); + + await expect( + Effect.runPromise( + stringProvider.sendTransaction(transaction, transactionMeta) + ) + ).resolves.toBe("legacy-broadcast-hash"); + + const error = await Effect.runPromise( + Effect.flip(errorProvider.sendTransaction(transaction, transactionMeta)) + ); + expect(error).toBeInstanceOf(ExternalProviderError); + expect((error as ExternalProviderError).customMessage).toBe( + "Transaction blocked by host policy" + ); + }); +}); diff --git a/packages/widget/tests/providers/persistence.test.ts b/packages/widget/tests/providers/persistence.test.ts new file mode 100644 index 000000000..2e779d382 --- /dev/null +++ b/packages/widget/tests/providers/persistence.test.ts @@ -0,0 +1,129 @@ +import { Layer, Option, Schema } from "effect"; +import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { + setTosAcceptedAtom, + tosAcceptedAtom, +} from "../../src/features/preferences/state/tos-atoms"; +import { + StoredPublicKeys, + TosAccepted, + WidgetPersistence, + widgetStorageKeys, +} from "../../src/services/persistence/widget-persistence"; + +class MemoryStorage implements Storage { + readonly values = new Map(); + reads = 0; + + get length() { + return this.values.size; + } + + clear() { + this.values.clear(); + } + + getItem(key: string) { + this.reads += 1; + return this.values.get(key) ?? null; + } + + key(index: number) { + return [...this.values.keys()][index] ?? null; + } + + removeItem(key: string) { + this.values.delete(key); + } + + setItem(key: string, value: string) { + this.values.set(key, value); + } +} + +const makeRegistry = (storage: Storage) => + AtomRegistry.make({ + initialValues: [ + [ + appRuntime.layer, + Layer.effect(WidgetPersistence, WidgetPersistence.make).pipe( + Layer.provide(KeyValueStore.layerStorage(() => storage)) + ), + ], + ], + }); + +describe("Effect browser persistence", () => { + it("preserves versioned keys and validates stored value schemas", () => { + expect(widgetStorageKeys).toEqual({ + skPubKeys: "sk-widget@1//skPubKeys", + tosAccepted: "sk-widget@1//tosAccepted", + }); + expect(Schema.decodeUnknownSync(TosAccepted)(true)).toBe(true); + expect( + Schema.decodeUnknownSync(StoredPublicKeys)({ cosmos: "public-key" }) + ).toEqual({ cosmos: "public-key" }); + expect(() => Schema.decodeUnknownSync(TosAccepted)("true")).toThrow(); + expect(() => + Schema.decodeUnknownSync(StoredPublicKeys)({ cosmos: 1 }) + ).toThrow(); + }); + + it("reads existing values once and shares them through the atom registry", () => { + const storage = new MemoryStorage(); + storage.values.set(widgetStorageKeys.tosAccepted, "true"); + storage.values.set( + widgetStorageKeys.skPubKeys, + JSON.stringify({ cosmos: "public-key" }) + ); + const registry = makeRegistry(storage); + + expect(AsyncResult.getOrThrow(registry.get(tosAcceptedAtom))).toBe(true); + expect(AsyncResult.getOrThrow(registry.get(tosAcceptedAtom))).toBe(true); + expect(storage.reads).toBe(1); + }); + + it("uses declared defaults when persisted values are absent", () => { + const registry = makeRegistry(new MemoryStorage()); + + expect(AsyncResult.getOrThrow(registry.get(tosAcceptedAtom))).toBe(false); + }); + + it("exposes malformed persisted values as schema failures", () => { + const storage = new MemoryStorage(); + storage.values.set(widgetStorageKeys.tosAccepted, '"not-a-boolean"'); + const registry = makeRegistry(storage); + const result = registry.get(tosAcceptedAtom); + + expect(AsyncResult.isFailure(result)).toBe(true); + expect(Option.isNone(AsyncResult.value(result))).toBe(true); + }); + + it("publishes writes and exposes write failures through mutation state", () => { + const storage = new MemoryStorage(); + const registry = makeRegistry(storage); + + registry.set(setTosAcceptedAtom, true); + + expect( + AsyncResult.getOrThrow(registry.get(setTosAcceptedAtom)) + ).toBeUndefined(); + expect(AsyncResult.getOrThrow(registry.get(tosAcceptedAtom))).toBe(true); + expect(storage.values.get(widgetStorageKeys.tosAccepted)).toBe("true"); + + const failingStorage = new MemoryStorage(); + failingStorage.setItem = () => { + throw new Error("storage blocked"); + }; + const failingRegistry = makeRegistry(failingStorage); + failingRegistry.set(setTosAcceptedAtom, true); + + expect(AsyncResult.isFailure(failingRegistry.get(setTosAcceptedAtom))).toBe( + true + ); + }); +}); diff --git a/packages/widget/tests/providers/prepare-ledger-live-transaction.test.ts b/packages/widget/tests/providers/prepare-ledger-live-transaction.test.ts index f823db2a7..00302c35a 100644 --- a/packages/widget/tests/providers/prepare-ledger-live-transaction.test.ts +++ b/packages/widget/tests/providers/prepare-ledger-live-transaction.test.ts @@ -1,11 +1,13 @@ +import { Result, Schema } from "effect"; import { describe, expect, it, vi } from "vitest"; +import { ValidatorAddress } from "../../src/domain/schema/identifiers"; import { CosmosNetworks, MiscNetworks, SubstrateNetworks, } from "../../src/domain/types/chains/networks"; -import type { SKTxMeta } from "../../src/domain/types/wallets/generic-wallet"; -import { prepareLedgerLiveTransaction } from "../../src/providers/ledger/prepare-ledger-live-transaction"; +import type { SKTxMeta } from "../../src/public-api/types"; +import { prepareLedgerLiveTransaction } from "../../src/services/wallet/connectors/ledger/prepare-ledger-live-transaction"; const substrateMethod = vi.hoisted(() => ({ current: { @@ -56,6 +58,8 @@ const createTxMeta = (overrides: Partial): SKTxMeta => ...overrides, }) as SKTxMeta; +const validatorAddress = Schema.decodeSync(ValidatorAddress); + const tronTx = JSON.stringify({ raw_data: { contract: [], @@ -105,15 +109,19 @@ describe("prepareLedgerLiveTransaction", () => { }, rawArguments: { amount: "1201", - validatorAddresses: ["validator-1", "validator-2", "validator-3"], + validatorAddresses: [ + validatorAddress("validator-1"), + validatorAddress("validator-2"), + validatorAddress("validator-3"), + ], }, txType: "VOTE", }), }); - expect(result.isRight()).toBe(true); + expect(Result.isSuccess(result)).toBe(true); - const tx = result.unsafeCoerce() as { + const tx = Result.getOrThrow(result) as { amount: string; votes: Array<{ address: string; voteCount: number }>; }; @@ -134,15 +142,15 @@ describe("prepareLedgerLiveTransaction", () => { amount: "0.123456", rawArguments: { amount: "0.123456", - validatorAddress: "cosmosvaloper1validator", + validatorAddress: validatorAddress("cosmosvaloper1validator"), }, txType: "CLAIM_REWARDS", }), }); - expect(result.isRight()).toBe(true); + expect(Result.isSuccess(result)).toBe(true); - const tx = result.unsafeCoerce() as { + const tx = Result.getOrThrow(result) as { amount: string; mode: string; validators: Array<{ address: string; amount: string }>; @@ -162,15 +170,15 @@ describe("prepareLedgerLiveTransaction", () => { txMeta: createTxMeta({ amount: null, rawArguments: { - validatorAddress: "cosmosvaloper1validator", + validatorAddress: validatorAddress("cosmosvaloper1validator"), }, txType: "CLAIM_REWARDS", }), }); - expect(result.isRight()).toBe(true); + expect(Result.isSuccess(result)).toBe(true); - const tx = result.unsafeCoerce() as { + const tx = Result.getOrThrow(result) as { amount: string; validators: Array<{ address: string; amount: string }>; }; @@ -188,13 +196,13 @@ describe("prepareLedgerLiveTransaction", () => { txMeta: createTxMeta({ amount: null, rawArguments: { - validatorAddress: "cosmosvaloper1validator", + validatorAddress: validatorAddress("cosmosvaloper1validator"), }, txType: "STAKE", }), }); - expect(result.extract()).toBe("Missing Cosmos Ledger arguments"); + expect(Result.merge(result)).toBe("Missing Cosmos Ledger arguments"); }); it("uses the Polkadot bond payee as Ledger reward destination", () => { @@ -222,9 +230,9 @@ describe("prepareLedgerLiveTransaction", () => { }), }); - expect(result.isRight()).toBe(true); + expect(Result.isSuccess(result)).toBe(true); - const tx = result.unsafeCoerce() as { + const tx = Result.getOrThrow(result) as { amount: string; rewardDestination?: string; }; diff --git a/packages/widget/tests/providers/public-api-contract.dom.test.ts b/packages/widget/tests/providers/public-api-contract.dom.test.ts new file mode 100644 index 000000000..684fd17ff --- /dev/null +++ b/packages/widget/tests/providers/public-api-contract.dom.test.ts @@ -0,0 +1,39 @@ +import { describe, expectTypeOf, it } from "vitest"; +import type * as bundleEntry from "../../src/index.bundle"; +import type * as packageEntry from "../../src/index.package"; +import type { + BundledSKWidgetProps, + SKWallet as BundleWallet, + BundledSKWidgetProps as DeclaredBundledSKWidgetProps, + SKWallet as DeclaredBundleWallet, + SKWallet as DeclaredPackageWallet, + SKAppProps as DeclaredSKAppProps, + SKWallet as PackageWallet, + SKAppProps, +} from "../../src/public-api/types"; + +type PublicBundle = typeof import("../../src/public-api/index.bundle"); +type PublicPackage = typeof import("../../src/public-api/index.package"); + +describe("public declaration contracts", () => { + it("keeps the package runtime exports compatible with the public facade", () => { + expectTypeOf().toEqualTypeOf< + keyof PublicPackage + >(); + expectTypeOf().toExtend(); + }); + + it("keeps the bundled runtime exports compatible with the public facade", () => { + expectTypeOf().toEqualTypeOf< + keyof PublicBundle + >(); + expectTypeOf().toExtend(); + }); + + it("keeps the exported consumer types identical to the public contracts", () => { + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); +}); diff --git a/packages/widget/tests/providers/rich-error-service.test.ts b/packages/widget/tests/providers/rich-error-service.test.ts new file mode 100644 index 000000000..4910450b1 --- /dev/null +++ b/packages/widget/tests/providers/rich-error-service.test.ts @@ -0,0 +1,85 @@ +import { Effect, Layer, Stream } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { richErrorAtom } from "../../src/features/widget-shell/react/use-rich-errors"; +import { WidgetConfigService } from "../../src/services/config/widget-config"; +import { RichErrorService } from "../../src/services/errors/rich-error-service"; + +const richErrorServiceAtom = appRuntime.atom( + Effect.map(RichErrorService, (service) => service) +); + +const makeRegistry = (baseUrl: string) => + AtomRegistry.make({ + initialValues: [ + [ + appRuntime.layer, + RichErrorService.layer.pipe( + Layer.provide( + WidgetConfigService.layer({ + initial: normalizeWidgetConfig({ + apiKey: "", + baseUrl, + variant: "default", + }), + changes: Stream.never, + current: Effect.never, + }) + ), + Layer.fresh + ), + ], + ], + }); + +describe("rich error service", () => { + it("publishes, resets, and isolates registry state", async () => { + const first = makeRegistry("https://first.example.com"); + const second = makeRegistry("https://second.example.com"); + const unmountFirst = first.mount(richErrorAtom); + const unmountSecond = second.mount(richErrorAtom); + + try { + const firstService = AsyncResult.getOrThrow( + first.get(richErrorServiceAtom) + ); + const secondService = AsyncResult.getOrThrow( + second.get(richErrorServiceAtom) + ); + + await Effect.runPromise( + firstService.publishResponse({ + data: { message: "First failure" }, + url: "https://first.example.com/v1/tokens", + }) + ); + + await vi.waitFor(() => { + expect(AsyncResult.getOrThrow(first.get(richErrorAtom))).toEqual({ + message: "First failure", + }); + }); + expect(AsyncResult.getOrThrow(second.get(richErrorAtom))).toBeNull(); + + first.set(richErrorAtom, null); + expect(AsyncResult.getOrThrow(first.get(richErrorAtom))).toBeNull(); + expect(AsyncResult.getOrThrow(second.get(richErrorAtom))).toBeNull(); + + await Effect.runPromise( + secondService.publishResponse({ + data: { message: "Ignored wrong origin" }, + url: "https://first.example.com/v1/tokens", + }) + ); + expect(AsyncResult.getOrThrow(second.get(richErrorAtom))).toBeNull(); + } finally { + unmountFirst(); + unmountSecond(); + first.dispose(); + second.dispose(); + } + }); +}); diff --git a/packages/widget/tests/providers/runtime-generation-key.test.ts b/packages/widget/tests/providers/runtime-generation-key.test.ts new file mode 100644 index 000000000..8da90220f --- /dev/null +++ b/packages/widget/tests/providers/runtime-generation-key.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it, vi } from "vitest"; +import { makeWidgetRuntimeGenerationKey } from "../../src/app/config/runtime-generation"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; + +const settings = normalizeWidgetConfig({ + apiKey: "api-key", + baseUrl: "https://legacy.example/", + borrowApiUrl: "https://borrow.example/", + variant: "default", + yieldsApiUrl: "https://yield.example/", +}); + +describe("WidgetRuntimeGenerationKey", () => { + it("is equal for reconstructed API configuration", () => { + const first = makeWidgetRuntimeGenerationKey(settings); + const equivalent = makeWidgetRuntimeGenerationKey({ ...settings }); + + expect(first).toBe(equivalent); + }); + + it("ignores live tracking callback identity", () => { + const first = makeWidgetRuntimeGenerationKey({ + ...settings, + tracking: { trackEvent: vi.fn() }, + }); + const second = makeWidgetRuntimeGenerationKey({ + ...settings, + tracking: { trackEvent: vi.fn() }, + }); + + expect(first).toBe(second); + }); + + it.each([ + ["apiKey", "changed-api-key"], + ["baseUrl", "https://changed-legacy.example/"], + ["borrowApiUrl", "https://changed-borrow.example/"], + ["yieldsApiUrl", "https://changed-yield.example/"], + ] as const)("changes identity when %s changes", (field, value) => { + const first = makeWidgetRuntimeGenerationKey(settings); + const changed = makeWidgetRuntimeGenerationKey({ + ...settings, + [field]: value, + }); + + expect(first).not.toBe(changed); + }); +}); diff --git a/packages/widget/tests/providers/runtime-generation.dom.test.tsx b/packages/widget/tests/providers/runtime-generation.dom.test.tsx new file mode 100644 index 000000000..59fa6f7ad --- /dev/null +++ b/packages/widget/tests/providers/runtime-generation.dom.test.tsx @@ -0,0 +1,426 @@ +import { useAtom, useAtomSet, useAtomValue } from "@effect/atom-react"; +import { Deferred, Effect, Schema } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import { act } from "react"; +import type { DataRouter } from "react-router"; +import { describe, expect, it, vi } from "vitest"; +import { SKAtomRegistryProvider } from "../../src/app/composition/providers/atom-runtime"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { applicationRouterAtom } from "../../src/app/runtime/application-router-runtime"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + classicFlowSessionStore, + makeStartClassicFlowSession, +} from "../../src/features/classic-transaction-flow/facade"; +import type { ClassicTransactionFlowIntake } from "../../src/features/classic-transaction-flow/model/classic-transaction-flow"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiActionFixture, yieldApiYieldFixture } from "../fixtures"; +import { render } from "../utils/test-utils.dom"; + +type LifecycleProbe = { + initialized: number; + disposed: number; +}; + +type WorkflowProbe = { + readonly deferredSigning: Deferred.Deferred; + finalized: number; + machine: object | null; + starts: number; + states: string[]; + submissions: number; + walletPrompts: number; +}; + +const stagedWorkflowAtom = Atom.make("empty"); +const trackingServiceProbeAtom = appRuntime.atom( + TrackingService.use((tracking) => Effect.succeed(tracking)) +); +const runtimeLifecycleAtom = Atom.family((probe: LifecycleProbe) => + appRuntime.atom( + Effect.acquireRelease( + Effect.sync(() => { + probe.initialized += 1; + return probe.initialized; + }), + () => + Effect.sync(() => { + probe.disposed += 1; + }) + ) + ) +); + +const controllableWorkflowAtom = Atom.family((probe: WorkflowProbe) => + appRuntime.atom( + Effect.gen(function* () { + const machine = {}; + probe.machine = machine; + probe.starts += 1; + + yield* Effect.gen(function* () { + probe.walletPrompts += 1; + probe.states.push("Signing"); + yield* Deferred.await(probe.deferredSigning); + probe.submissions += 1; + probe.states.push("Completed"); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + probe.finalized += 1; + }) + ), + Effect.forkScoped + ); + + return machine; + }) + ) +); + +const ControllableWorkflow = ({ probe }: { readonly probe: WorkflowProbe }) => { + const machine = useAtomValue(controllableWorkflowAtom(probe)); + + return ( + + {AsyncResult.isSuccess(machine) ? "active" : "loading"} + + ); +}; + +const RuntimeHarness = ({ + probe, + workflowProbe, +}: { + readonly probe: LifecycleProbe; + readonly workflowProbe?: WorkflowProbe; +}) => { + const lifecycle = useAtomValue(runtimeLifecycleAtom(probe)); + const tracking = useAtomValue(trackingServiceProbeAtom); + const [staged, setStaged] = useAtom(stagedWorkflowAtom); + + return ( + <> + + {AsyncResult.isSuccess(lifecycle) ? lifecycle.value : "loading"} + + {staged} + {workflowProbe && staged === "active-workflow" ? ( + + ) : null} + + + + ); +}; + +const activityIntake = (): ClassicTransactionFlowIntake => { + const selectedYield = yieldApiYieldFixture(); + return { + _tag: "ActivityResume", + action: yieldApiActionFixture(), + providersDetails: [], + selectedValidators: [], + selectedYield, + walletScope: new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)("0xWallet"), + network: "ethereum", + }), + }; +}; + +const ClassicFlowRuntimeHarness = () => { + const session = useAtomValue(classicFlowSessionStore.currentSessionAtom); + const start = useAtomSet(classicFlowSessionStore.startAtom); + + return ( + <> + + {session ? "present" : "none"} + + + + ); +}; + +const ApplicationRouterHarness = ({ + capture, +}: { + readonly capture: (router: DataRouter) => void; +}) => { + const router = useAtomValue(applicationRouterAtom); + + return ( + + ); +}; + +const settings = (trackEvent: (event: string) => void, apiKey = "api-key") => + normalizeWidgetConfig({ + apiKey, + tracking: { trackEvent }, + variant: "default", + }); + +describe("API runtime generations", () => { + it("preserves router history for live settings and replaces it with API identity", async () => { + const firstTrack = vi.fn(); + const secondTrack = vi.fn(); + const routers: DataRouter[] = []; + const app = await render( + + routers.push(router)} /> + + ); + const capture = () => + app.container.querySelector("button")?.click(); + + await act(async () => capture()); + const firstRouter = routers[0]; + if (!firstRouter) throw new Error("Expected the first application router"); + + await act(async () => { + await firstRouter.navigate("/review"); + }); + + await app.rerender( + + routers.push(router)} /> + + ); + await act(async () => capture()); + + expect(routers[1]).toBe(firstRouter); + expect(routers[1]?.state.location.pathname).toBe("/review"); + + const dispose = vi.spyOn(firstRouter, "dispose"); + await app.rerender( + + routers.push(router)} /> + + ); + await vi.waitFor(() => expect(dispose).toHaveBeenCalledOnce()); + await act(async () => capture()); + + expect(routers[2]).not.toBe(firstRouter); + expect(routers[2]?.state.location.pathname).toBe("/"); + }); + + it("retains intake for live settings and clears it on runtime replacement before routing", async () => { + const firstTrack = vi.fn(); + const secondTrack = vi.fn(); + const app = await render( + + + + ); + + await act(async () => + app.container.querySelector("button")?.click() + ); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="classic-flow-session"]') + ?.textContent + ).not.toBe("none") + ); + const sessionKey = app.container.querySelector( + '[data-testid="classic-flow-session"]' + )?.textContent; + + await app.rerender( + + + + ); + expect( + app.container.querySelector('[data-testid="classic-flow-session"]') + ?.textContent + ).toBe(sessionKey); + + await app.rerender( + + + + ); + await vi.waitFor(() => + expect( + app.container.querySelector('[data-testid="classic-flow-session"]') + ?.textContent + ).toBe("none") + ); + }); + + it("keeps the runtime and staged state while resolving new live callbacks", async () => { + const firstTrack = vi.fn(); + const secondTrack = vi.fn(); + const probe: LifecycleProbe = { disposed: 0, initialized: 0 }; + const workflowProbe: WorkflowProbe = { + deferredSigning: await Effect.runPromise(Deferred.make()), + finalized: 0, + machine: null, + starts: 0, + states: [], + submissions: 0, + walletPrompts: 0, + }; + const app = await render( + + + + ); + + await vi.waitFor(() => expect(probe.initialized).toBe(1)); + await act(async () => + app.container.querySelector("button")?.click() + ); + await vi.waitFor(() => expect(workflowProbe.starts).toBe(1)); + const machine = workflowProbe.machine; + await act(async () => + [ + ...app.container.querySelectorAll("button"), + ][1]?.click() + ); + await vi.waitFor(() => expect(firstTrack).toHaveBeenCalledOnce()); + + await app.rerender( + + + + ); + await act(async () => + [ + ...app.container.querySelectorAll("button"), + ][1]?.click() + ); + + await vi.waitFor(() => expect(secondTrack).toHaveBeenCalledOnce()); + expect(firstTrack).toHaveBeenCalledOnce(); + expect(probe).toEqual({ disposed: 0, initialized: 1 }); + expect(workflowProbe).toMatchObject({ + finalized: 0, + machine, + starts: 1, + states: ["Signing"], + submissions: 0, + walletPrompts: 1, + }); + expect( + app.container.querySelector('[data-testid="staged"]')?.textContent + ).toBe("active-workflow"); + + await Effect.runPromise( + Deferred.succeed(workflowProbe.deferredSigning, undefined) + ); + await vi.waitFor(() => expect(workflowProbe.submissions).toBe(1)); + expect(workflowProbe).toMatchObject({ + machine, + starts: 1, + states: ["Signing", "Completed"], + submissions: 1, + walletPrompts: 1, + }); + }); + + it("disposes all old registry state when API configuration changes", async () => { + const probe: LifecycleProbe = { disposed: 0, initialized: 0 }; + const workflowProbe: WorkflowProbe = { + deferredSigning: await Effect.runPromise(Deferred.make()), + finalized: 0, + machine: null, + starts: 0, + states: [], + submissions: 0, + walletPrompts: 0, + }; + const track = vi.fn(); + const app = await render( + + + + ); + + await vi.waitFor(() => expect(probe.initialized).toBe(1)); + await act(async () => + app.container.querySelector("button")?.click() + ); + await vi.waitFor(() => expect(workflowProbe.starts).toBe(1)); + + await app.rerender( + + + + ); + + await vi.waitFor(() => + expect(probe).toEqual({ disposed: 1, initialized: 2 }) + ); + expect( + app.container.querySelector('[data-testid="staged"]')?.textContent + ).toBe("empty"); + await vi.waitFor(() => expect(workflowProbe.finalized).toBe(1)); + + await Effect.runPromise( + Deferred.succeed(workflowProbe.deferredSigning, undefined) + ); + expect(workflowProbe).toMatchObject({ + finalized: 1, + starts: 1, + submissions: 0, + walletPrompts: 1, + }); + }); +}); diff --git a/packages/widget/tests/providers/theme-wrapper.test.ts b/packages/widget/tests/providers/theme-wrapper.test.ts index 7275141c4..932254e82 100644 --- a/packages/widget/tests/providers/theme-wrapper.test.ts +++ b/packages/widget/tests/providers/theme-wrapper.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "vitest"; -import { getThemeOverrides } from "../../src/providers/theme-wrapper"; -import { darkTheme, lightTheme } from "../../src/styles/theme/themes"; -import { portoThemeOverrides } from "../../src/styles/theme/variant-overrides/porto"; -import { utilaThemeOverrides } from "../../src/styles/theme/variant-overrides/utila"; +import { getThemeOverrides } from "../../src/app/composition/providers/theme-wrapper"; +import { darkTheme, lightTheme } from "../../src/shared/styles/theme/themes"; +import { portoThemeOverrides } from "../../src/shared/styles/theme/variant-overrides/porto"; +import { utilaThemeOverrides } from "../../src/shared/styles/theme/variant-overrides/utila"; describe("getThemeOverrides", () => { it("keeps the default variant unthemed", () => { diff --git a/packages/widget/tests/providers/tracking-ingress.dom.test.tsx b/packages/widget/tests/providers/tracking-ingress.dom.test.tsx new file mode 100644 index 000000000..762bf895a --- /dev/null +++ b/packages/widget/tests/providers/tracking-ingress.dom.test.tsx @@ -0,0 +1,52 @@ +import type { PropsWithChildren } from "react"; +import { describe, expect, it, vi } from "vitest"; +import { TrackingContextProvider } from "../../src/app/composition/providers/tracking"; +import { useTrackEvent } from "../../src/features/tracking/react/use-track-event"; +import { useTrackPage } from "../../src/features/tracking/react/use-track-page"; +import { renderHook } from "../utils/test-utils.dom"; + +describe("public tracking ingress", () => { + it("delivers event and page commands to host and variant adapters", async () => { + const tracking = { + trackEvent: vi.fn(), + trackPageView: vi.fn(), + }; + const variantTracking = { + trackEvent: vi.fn(), + trackPageView: vi.fn(), + }; + const wrapper = ({ children }: PropsWithChildren) => ( + + {children} + + ); + const hook = await renderHook( + () => { + useTrackPage("earn", { source: "test" }); + return useTrackEvent(); + }, + { wrapper } + ); + + hook.result.current("txSigned", { txId: "transaction-1" }); + + await vi.waitFor(() => { + expect(tracking.trackEvent).toHaveBeenCalledWith("Transaction signed", { + txId: "transaction-1", + }); + expect(tracking.trackPageView).toHaveBeenCalledWith("Earn", { + source: "test", + }); + expect(variantTracking.trackEvent).toHaveBeenCalledWith( + "Transaction signed", + { txId: "transaction-1" } + ); + expect(variantTracking.trackPageView).toHaveBeenCalledWith("Earn", { + source: "test", + }); + }); + }); +}); diff --git a/packages/widget/tests/providers/tracking-service.test.ts b/packages/widget/tests/providers/tracking-service.test.ts new file mode 100644 index 000000000..a5d30c303 --- /dev/null +++ b/packages/widget/tests/providers/tracking-service.test.ts @@ -0,0 +1,111 @@ +import { Effect, Layer, Stream } from "effect"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { WidgetConfigService } from "../../src/services/config/widget-config"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; + +const variantTracking = vi.hoisted(() => ({ + initMixpanel: vi.fn(), + trackEvent: vi.fn(), + trackPageView: vi.fn(), +})); + +vi.mock("../../src/services/tracking/tracking-variants", () => ({ + initMixpanel: variantTracking.initMixpanel, + tracking: { + trackEvent: variantTracking.trackEvent, + trackPageView: variantTracking.trackPageView, + }, +})); + +describe("tracking service", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("resolves the latest live tracking configuration per invocation", async () => { + const firstTrackEvent = vi.fn(); + const secondTrackEvent = vi.fn(); + const trackPageView = vi.fn(); + let input = normalizeWidgetConfig({ + apiKey: "", + tracking: { trackEvent: firstTrackEvent, trackPageView }, + variant: "default", + }); + const layer = TrackingService.layer.pipe( + Layer.provide( + WidgetConfigService.layer({ + initial: input, + changes: Stream.never, + current: Effect.sync(() => input), + }) + ) + ); + + await Effect.runPromise( + TrackingService.use((tracking) => + Effect.gen(function* () { + yield* tracking.trackEvent("txSigned", { txId: "first" }); + input = normalizeWidgetConfig({ + apiKey: "", + tracking: { trackEvent: secondTrackEvent, trackPageView }, + variant: "default", + }); + yield* tracking.trackEvent("txSubmitted", { txId: "second" }); + yield* tracking.trackPageView("earn", { source: "test" }); + }) + ).pipe(Effect.provide(layer)) + ); + + expect(firstTrackEvent).toHaveBeenCalledOnce(); + expect(firstTrackEvent).toHaveBeenCalledWith("Transaction signed", { + txId: "first", + }); + expect(secondTrackEvent).toHaveBeenCalledOnce(); + expect(secondTrackEvent).toHaveBeenCalledWith("Transaction submitted", { + txId: "second", + }); + expect(trackPageView).toHaveBeenCalledWith("Earn", { source: "test" }); + }); + + it("initializes variant tracking once during layer construction", async () => { + const input = normalizeWidgetConfig({ + apiKey: "", + chainModal: () => null, + tracking: undefined, + variant: "zerion", + }); + const layer = TrackingService.layer.pipe( + Layer.provide( + WidgetConfigService.layer({ + initial: input, + changes: Stream.never, + current: Effect.succeed(input), + }) + ) + ); + + await Effect.runPromise( + TrackingService.use((tracking) => + Effect.all([ + tracking.trackEvent("txSigned", { txId: "first" }), + tracking.trackEvent("txSubmitted", { txId: "second" }), + tracking.trackPageView("positions"), + ]) + ).pipe(Effect.provide(layer)) + ); + + expect(variantTracking.initMixpanel).toHaveBeenCalledTimes(1); + expect(variantTracking.trackEvent).toHaveBeenNthCalledWith( + 1, + "Transaction signed", + { txId: "first" } + ); + expect(variantTracking.trackEvent).toHaveBeenNthCalledWith( + 2, + "Transaction submitted", + { txId: "second" } + ); + expect(variantTracking.trackPageView).toHaveBeenCalledWith("Positions"); + }); +}); diff --git a/packages/widget/tests/providers/wagmi-connection-characterization.test.ts b/packages/widget/tests/providers/wagmi-connection-characterization.test.ts new file mode 100644 index 000000000..25d649d98 --- /dev/null +++ b/packages/widget/tests/providers/wagmi-connection-characterization.test.ts @@ -0,0 +1,99 @@ +import { createClient, type Hex } from "viem"; +import { mainnet } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; +import { createConfig, http } from "wagmi"; +import { + connect, + disconnect, + getConnection, + watchConnection, +} from "wagmi/actions"; +import { mock } from "wagmi/connectors"; + +const account = "0x0000000000000000000000000000000000000001" satisfies Hex; + +const makeWagmiConfig = () => + createConfig({ + chains: [mainnet], + client: ({ chain }) => createClient({ chain, transport: http() }), + connectors: [mock({ accounts: [account] })], + }); + +describe("Wagmi core connection characterization", () => { + it("seeds the disconnected snapshot and watches subsequent changes", async () => { + const wagmiConfig = makeWagmiConfig(); + const onChange = vi.fn(); + + expect(getConnection(wagmiConfig)).toEqual({ + address: undefined, + addresses: undefined, + chain: undefined, + chainId: undefined, + connector: undefined, + isConnected: false, + isConnecting: false, + isDisconnected: true, + isReconnecting: false, + status: "disconnected", + }); + + const unsubscribe = watchConnection(wagmiConfig, { onChange }); + await connect(wagmiConfig, { connector: wagmiConfig.connectors[0] }); + + expect(getConnection(wagmiConfig)).toMatchObject({ + address: account, + addresses: [account], + chainId: mainnet.id, + isConnected: true, + status: "connected", + }); + expect(onChange).toHaveBeenCalled(); + expect(onChange.mock.calls.at(-1)?.[0]).toMatchObject({ + address: account, + status: "connected", + }); + + const publicationsBeforeCleanup = onChange.mock.calls.length; + unsubscribe(); + await disconnect(wagmiConfig); + + expect(onChange).toHaveBeenCalledTimes(publicationsBeforeCleanup); + }); + + it("keeps replacement configuration publications scoped to its watch", async () => { + const firstConfig = makeWagmiConfig(); + const replacementConfig = makeWagmiConfig(); + const publications: Array<{ + readonly owner: "first" | "replacement"; + readonly status: string; + }> = []; + const unsubscribeFirst = watchConnection(firstConfig, { + onChange: (snapshot) => + publications.push({ owner: "first", status: snapshot.status }), + }); + + unsubscribeFirst(); + const unsubscribeReplacement = watchConnection(replacementConfig, { + onChange: (snapshot) => + publications.push({ + owner: "replacement", + status: snapshot.status, + }), + }); + + await connect(firstConfig, { connector: firstConfig.connectors[0] }); + await connect(replacementConfig, { + connector: replacementConfig.connectors[0], + }); + unsubscribeReplacement(); + + expect(publications).not.toContainEqual({ + owner: "first", + status: "connected", + }); + expect(publications).toContainEqual({ + owner: "replacement", + status: "connected", + }); + }); +}); diff --git a/packages/widget/tests/providers/wagmi-provider.browser.test.tsx b/packages/widget/tests/providers/wagmi-provider.browser.test.tsx new file mode 100644 index 000000000..554feac22 --- /dev/null +++ b/packages/widget/tests/providers/wagmi-provider.browser.test.tsx @@ -0,0 +1,500 @@ +import { RegistryProvider, useAtomValue } from "@effect/atom-react"; +import { + type Adapter, + type WalletName, + WalletReadyState, +} from "@solana/wallet-adapter-base"; +import type { Connection } from "@solana/web3.js"; +import { Array as EArray, Effect, Layer, Queue, Stream } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { HttpResponse, http } from "msw"; +import { + Component, + type PropsWithChildren, + StrictMode, + useContext, + useEffect, +} from "react"; +import type { Address } from "viem"; +import { + type Config, + createConfig, + createConnector, + useAccount, + useConnect, + useConnectors, + useDisconnect, + useSwitchChain, + WagmiContext, + http as wagmiHttp, +} from "wagmi"; +import { watchConnectors } from "wagmi/actions"; +import { optimism } from "wagmi/chains"; +import { ThirdPartyQueryClientProvider } from "../../src/app/composition/providers/query-client"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import { solana } from "../../src/domain/types/chains/misc"; +import { EvmNetworks } from "../../src/domain/types/chains/networks"; +import { WagmiConfigProvider } from "../../src/features/wallet/react/provider"; +import { + currentWalletStateResultAtom, + useWalletConfig, +} from "../../src/features/wallet/state/root-atom"; +import type { SolanaRuntime } from "../../src/services/wallet/platform/solana-platform"; +import { installSolanaConnectorMembership } from "../../src/services/wallet/solana-connector-membership"; +import type { + SolanaWalletDescriptor, + SolanaWalletSnapshot, +} from "../../src/services/wallet/solana-runtime"; +import { WalletService } from "../../src/services/wallet/wallet-service"; +import { legacyApiRoute } from "../mocks/api-routes"; +import { mockDelay } from "../mocks/delay"; +import { TestAtomRuntimeProvider } from "../utils/atom-runtime-provider"; +import { rkMockWallet } from "../utils/mock-connector"; +import { describe, expect, it, vi } from "../utils/test-extend"; +import { render, renderHook } from "../utils/test-utils"; + +class RuntimeErrorBoundary extends Component< + PropsWithChildren<{ readonly onError: (error: unknown) => void }>, + { readonly failed: boolean } +> { + override state = { failed: false }; + + static getDerivedStateFromError() { + return { failed: true }; + } + + override componentDidCatch(error: unknown) { + this.props.onError(error); + } + + override render() { + return this.state.failed ?
runtime failed
: this.props.children; + } +} + +const useWagmiProviderContract = () => ({ + contextConfig: useContext(WagmiContext) as Config | undefined, + walletConfig: useWalletConfig(), +}); + +const ConfigObserver = ({ + onConfig, +}: { + readonly onConfig: (config: Config) => void; +}) => { + const walletConfig = useWalletConfig(); + + useEffect(() => { + if (walletConfig.data) onConfig(walletConfig.data); + }); + + return null; +}; + +const useRainbowKitWagmiContract = () => ({ + account: useAccount(), + connect: useConnect(), + connectors: useConnectors(), + contextConfig: useContext(WagmiContext) as Config | undefined, + walletConfig: useWalletConfig(), + disconnect: useDisconnect(), + switchChain: useSwitchChain(), + walletProjection: useAtomValue(currentWalletStateResultAtom), +}); + +const solanaAccount = "0x0000000000000000000000000000000000000501" as Address; + +const makeSolanaAdapter = (name: string) => + ({ name: name as WalletName }) as Adapter; + +const makeSolanaDescriptor = ( + adapter: Adapter, + source: SolanaWalletDescriptor["source"], + readyState: WalletReadyState +): SolanaWalletDescriptor => ({ adapter, readyState, source }); + +const makeSolanaConnectorFactory = (wallet: SolanaWalletDescriptor) => + createConnector((config) => ({ + $filteredChains: Stream.succeed([solana]), + id: wallet.adapter.name, + isSolanaConnector: true, + name: wallet.adapter.name, + rkDetails: { + groupName: "Solana", + installed: + wallet.readyState === WalletReadyState.Installed || + wallet.readyState === WalletReadyState.Loadable, + }, + solanaAdapter: wallet.adapter, + solanaAdapterSource: wallet.source, + type: `solana-${wallet.source}`, + connect: async (parameters) => + ({ + accounts: parameters?.withCapabilities + ? [{ address: solanaAccount, capabilities: {} }] + : [solanaAccount], + chainId: solana.id, + }) as never, + disconnect: async () => undefined, + getAccounts: async () => [solanaAccount], + getChainId: async () => solana.id, + getProvider: async () => ({}), + isAuthorized: async () => false, + onAccountsChanged: () => undefined, + onChainChanged: () => undefined, + onDisconnect: () => config.emitter.emit("disconnect"), + sendTransaction: async () => "signature", + })); + +const makeSolanaRuntime = (initial: SolanaWalletSnapshot) => { + const listeners = new Set<() => Promise | void>(); + let snapshot = initial; + const runtime = { + connection: {} as Connection, + current: Effect.sync(() => snapshot), + states: Stream.callback((queue) => + Effect.acquireRelease( + Effect.sync(() => { + const listener = () => { + Queue.offerUnsafe(queue, snapshot); + }; + listeners.add(listener); + return listener; + }), + (listener) => Effect.sync(() => listeners.delete(listener)) + ) + ), + } satisfies SolanaRuntime; + + return { + emit: async (next: SolanaWalletSnapshot) => { + snapshot = next; + await Promise.all([...listeners].map((listener) => listener())); + }, + listenerCount: () => listeners.size, + runtime, + }; +}; + +const ControllerHarness = ({ + forceWalletConnectOnly, + onConfig, +}: { + readonly forceWalletConnectOnly: boolean; + readonly onConfig: (config: Config) => void; +}) => ( + + + + + + + + + +); + +describe("WagmiConfigProvider", () => { + it("publishes dynamic Solana membership and same-uid readiness through useConnectors", async () => { + const fallbackAdapter = makeSolanaAdapter("Phantom"); + const standardAdapter = makeSolanaAdapter("Phantom"); + const fallback = makeSolanaDescriptor( + fallbackAdapter, + "fallback", + WalletReadyState.NotDetected + ); + const standard = makeSolanaDescriptor( + standardAdapter, + "standard", + WalletReadyState.NotDetected + ); + const readyStandard = makeSolanaDescriptor( + standardAdapter, + "standard", + WalletReadyState.Loadable + ); + const runtime = makeSolanaRuntime({ wallets: [fallback] }); + const config = createConfig({ + chains: [solana], + connectors: [makeSolanaConnectorFactory(fallback)], + transports: { [solana.id]: wagmiHttp() }, + }); + const coreSnapshots: ReadonlyArray[] = []; + const unsubscribeCore = watchConnectors(config, { + onChange: (connectors) => coreSnapshots.push(connectors), + }); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* installSolanaConnectorMembership({ + actions: { disconnect: () => Effect.void }, + config, + core: { + current: Effect.succeed({ + connection: {} as never, + connectors: config.connectors, + }), + states: Stream.concat( + Stream.succeed({ + connection: {} as never, + connectors: config.connectors, + }), + Stream.never + ), + }, + createConnector: (wallet) => + Effect.succeed(makeSolanaConnectorFactory(wallet)), + runtime: runtime.runtime, + }); + expect(runtime.listenerCount()).toBe(1); + const configIdentity = config; + const hook = yield* Effect.promise(() => + renderHook(() => useConnectors(), { + wrapper: ({ children }) => ( + + {children} + + ), + }) + ); + + yield* Effect.promise(() => + hook.act(() => runtime.emit({ wallets: [standard] })) + ); + yield* Effect.promise(() => + expect + .poll(() => hook.result.current[0]?.solanaAdapterSource) + .toBe("standard") + ); + const standardConnector = hook.result.current[0]!; + expect(standardConnector).toMatchObject({ + rkDetails: { installed: false }, + solanaAdapter: standardAdapter, + }); + + yield* Effect.promise(() => + hook.act(() => runtime.emit({ wallets: [readyStandard] })) + ); + yield* Effect.promise(() => + expect + .poll(() => { + const connector = hook.result.current[0]; + return connector && "rkDetails" in connector + ? (connector.rkDetails as { readonly installed: boolean }) + .installed + : false; + }) + .toBe(true) + ); + const readyConnector = hook.result.current[0]!; + expect(readyConnector).not.toBe(standardConnector); + expect(readyConnector.uid).toBe(standardConnector.uid); + expect(readyConnector.emitter).toBe(standardConnector.emitter); + expect(readyConnector).toMatchObject({ + rkDetails: { installed: true }, + solanaAdapter: standardAdapter, + }); + expect(config).toBe(configIdentity); + }) + ) + ); + + unsubscribeCore(); + expect(coreSnapshots).toHaveLength(2); + expect(coreSnapshots.at(-1)?.[0]).toMatchObject({ + rkDetails: { installed: true }, + uid: config.connectors[0]?.uid, + }); + }); + + it("stops providing the fallback when wallet bootstrap fails", async () => { + const cause = new Error("wallet bootstrap failed"); + const onError = vi.fn<(error: unknown) => void>(); + const app = await render( + + + +
provider ready
+
+
+
+ ); + + await expect.element(app.getByText("runtime failed")).toBeVisible(); + expect(onError).toHaveBeenCalledWith(cause); + }); + + it("uses the fallback only while loading, then provides the initialized config by reference", async ({ + worker, + }) => { + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([EvmNetworks.Ethereum]); + }) + ); + + const hook = await renderHook(useWagmiProviderContract, { + wrapper: ({ children }) => ( + + + + {children} + + + + ), + }); + + const fallbackConfig = hook.result.current.contextConfig; + expect(fallbackConfig).toBeDefined(); + expect(hook.result.current.walletConfig.isLoading).toBe(true); + + await expect + .poll( + () => ({ + data: Boolean(hook.result.current.walletConfig.data), + error: hook.result.current.walletConfig.error, + }), + { timeout: 10_000 } + ) + .toEqual({ data: true, error: undefined }); + + expect(hook.result.current.contextConfig).toBe( + hook.result.current.walletConfig.data + ); + expect(hook.result.current.contextConfig).not.toBe(fallbackConfig); + }); + + it("keeps the authoritative config across StrictMode rerenders", async ({ + worker, + }) => { + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), () => + HttpResponse.json([EvmNetworks.Ethereum]) + ) + ); + const onConfig = vi.fn<(config: Config) => void>(); + const renderHarness = (forceWalletConnectOnly: boolean) => ( + + ); + const app = await render(renderHarness(false)); + + await vi.waitFor(() => { + expect(onConfig).toHaveBeenCalled(); + }); + const firstConfig = onConfig.mock.lastCall?.[0]; + expect(firstConfig).toBeDefined(); + onConfig.mockClear(); + + await app.rerender(renderHarness(false)); + expect(onConfig).not.toHaveBeenCalled(); + }); + + it("keeps RainbowKit-facing actions on the authoritative config", async ({ + worker, + }) => { + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), () => + HttpResponse.json([EvmNetworks.Ethereum, EvmNetworks.Optimism]) + ) + ); + const account = "0x0000000000000000000000000000000000000001"; + const hook = await renderHook(useRainbowKitWagmiContract, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); + const initialConfig = hook.result.current.contextConfig; + + expect(initialConfig).toBeDefined(); + + await expect + .poll( + () => ({ + connected: hook.result.current.account.isConnected, + ready: Boolean(hook.result.current.walletConfig.data), + }), + { timeout: 10_000 } + ) + .toEqual({ connected: true, ready: true }); + if (initialConfig !== hook.result.current.walletConfig.data) { + expect(initialConfig?.state.connections.size).toBe(0); + } + expect(hook.result.current.contextConfig).toBe( + hook.result.current.walletConfig.data + ); + expect( + AsyncResult.getOrThrow(hook.result.current.walletProjection) + ).toMatchObject({ address: account, status: "connected" }); + + await hook.result.current.disconnect.disconnectAsync(); + await expect + .poll(() => ({ + account: hook.result.current.account.status, + projection: AsyncResult.getOrThrow(hook.result.current.walletProjection) + .status, + })) + .toEqual({ account: "disconnected", projection: "disconnected" }); + + await hook.result.current.connect.connectAsync({ + connector: EArray.getUnsafe(hook.result.current.connectors, 0), + }); + await expect + .poll(() => ({ + account: hook.result.current.account.status, + projection: AsyncResult.getOrThrow(hook.result.current.walletProjection) + .status, + })) + .toEqual({ account: "connected", projection: "connected" }); + + await hook.result.current.switchChain.switchChainAsync({ + chainId: optimism.id, + }); + await expect + .poll(() => ({ + account: hook.result.current.account.chainId, + projection: AsyncResult.getOrThrow(hook.result.current.walletProjection) + .chain?.id, + })) + .toEqual({ account: optimism.id, projection: optimism.id }); + }); +}); diff --git a/packages/widget/tests/providers/wallet-atoms.test.ts b/packages/widget/tests/providers/wallet-atoms.test.ts new file mode 100644 index 000000000..bbeda7e01 --- /dev/null +++ b/packages/widget/tests/providers/wallet-atoms.test.ts @@ -0,0 +1,339 @@ +import type { Connection as SolanaConnection } from "@solana/web3.js"; +import { Effect, Schema } from "effect"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector, createConfig } from "wagmi"; +import { AdditionalAddresses } from "../../src/domain/schema/address-models"; +import { InitParams } from "../../src/domain/schema/init-params"; +import { EnabledNetworksResponse } from "../../src/domain/schema/wallet-models"; +import { getConfig as getEvmConfig } from "../../src/services/wallet/connectors/ethereum/config"; +import { + makeInitializeWallet, + type WalletInitialConnectionInput, +} from "../../src/services/wallet/initial-connection"; +import { + WagmiOperations, + WagmiOperationsError, + type WagmiOperationsService, + wagmiOperations, +} from "../../src/services/wallet/platform/wagmi-operations"; +import { + buildWagmiConfig, + scopedMipdSubscription, +} from "../../src/services/wallet/wagmi-config"; + +const emptyInitParams = { + accountId: null, + balanceId: null, + network: null, + pendingaction: null, + tab: null, + token: null, + validator: null, + yieldId: null, +} as const; + +type InitialConnectionOperations = Pick< + WagmiOperationsService, + "connect" | "reconnect" | "switchChain" +>; + +const operationFailure = (cause: unknown) => + new WagmiOperationsError({ cause, operation: "reconnect" }); + +const makeInitializer = (operations: InitialConnectionOperations) => + Effect.runPromise( + makeInitializeWallet.pipe( + Effect.provideService( + WagmiOperations, + WagmiOperations.of({ ...wagmiOperations, ...operations }) + ) + ) + ); + +const initialize = async ( + operations: InitialConnectionOperations, + input: Omit< + WalletInitialConnectionInput, + "isLedgerDappBrowser" | "isMobileWallet" + > & { + readonly isLedgerDappBrowser?: boolean; + readonly isMobileWallet?: boolean; + } +) => { + const run = await makeInitializer(operations); + return Effect.runPromise( + run({ + ...input, + isLedgerDappBrowser: input.isLedgerDappBrowser ?? false, + isMobileWallet: input.isMobileWallet ?? false, + }) + ); +}; + +describe("wallet Effect Atom boundaries", () => { + it("decodes enabled networks into a validated set and rejects unknown values", () => { + const networks = Schema.decodeUnknownSync(EnabledNetworksResponse)([ + "ethereum", + "cosmos", + "ethereum", + ]); + + expect(networks).toEqual(new Set(["ethereum", "cosmos"])); + expect(() => + Schema.decodeUnknownSync(EnabledNetworksResponse)([ + "ethereum", + "not-a-network", + ]) + ).toThrow("not-a-network"); + }); + + it("decodes valid initialization parameters and ignores invalid fields", () => { + expect( + Schema.decodeUnknownSync(InitParams)({ + ...emptyInitParams, + network: "ethereum", + pendingaction: "UNSTAKE", + yieldId: "ethereum-eth-native-staking", + }) + ).toMatchObject({ + network: "ethereum", + pendingaction: "UNSTAKE", + yieldId: "ethereum-eth-native-staking", + }); + + expect( + Schema.decodeUnknownSync(InitParams)({ + ...emptyInitParams, + network: "ethereum-holesky", + }) + ).toMatchObject({ network: null }); + expect( + Schema.decodeUnknownSync(InitParams)({ + ...emptyInitParams, + pendingaction: "unstake", + }) + ).toMatchObject({ pendingaction: null }); + }); + + it("validates wallet-provided additional address data with Schema", () => { + const cosmosPubKey = "A".repeat(44); + + expect( + Schema.decodeUnknownSync(AdditionalAddresses)({ cosmosPubKey }) + ).toEqual({ cosmosPubKey }); + expect(() => + Schema.decodeUnknownSync(AdditionalAddresses)({ cosmosPubKey: "short" }) + ).toThrow(); + }); + + it("constructs EVM configuration directly from validated networks", async () => { + const config = await Effect.runPromise( + getEvmConfig({ + enabledNetworks: new Set(["ethereum"]), + forceWalletConnectOnly: true, + institutionalWallets: false, + variant: "default", + }) + ); + + expect(config.evmChains).toHaveLength(1); + expect(config.evmChainsMap.ethereum?.skChainName).toBe("ethereum"); + }); + + it("runs reconnect, mobile fallback, and requested chain switching in order", async () => { + const calls: string[] = []; + const injectedConnector = { id: "injected" } as Connector; + const wagmiConfig = { + connectors: [injectedConnector], + state: { chainId: 1 }, + } as unknown as ReturnType; + const operations: InitialConnectionOperations = { + reconnect: vi.fn(() => + Effect.sync(() => { + calls.push("reconnect"); + return []; + }) + ), + connect: vi.fn(() => + Effect.sync(() => { + calls.push("connect"); + return { accounts: [], chainId: 1 }; + }) + ), + switchChain: vi.fn(() => + Effect.sync(() => { + calls.push("switch"); + return { id: 2 }; + }) + ), + }; + + await Effect.runPromise( + (await makeInitializer(operations))({ + hasExternalProvider: false, + isLedgerDappBrowser: false, + isMobileWallet: true, + queryParamsInitChainId: 2, + wagmiConfig, + }) + ); + + expect(calls).toEqual(["reconnect", "connect", "switch"]); + expect(operations.reconnect).toHaveBeenCalledOnce(); + expect(operations.connect).toHaveBeenCalledOnce(); + expect(operations.switchChain).toHaveBeenCalledOnce(); + }); + + it("continues after reconnect, fallback connect, and initial switch failures", async () => { + const injectedConnector = { id: "injected" } as Connector; + const wagmiConfig = { + connectors: [injectedConnector], + state: { chainId: 1 }, + } as unknown as ReturnType; + const cause = new Error("initialization failed"); + const baseOperations: InitialConnectionOperations = { + connect: vi.fn(() => Effect.succeed({ accounts: [], chainId: 1 })), + reconnect: vi.fn(() => Effect.succeed([{} as never])), + switchChain: vi.fn(() => Effect.succeed({ id: 2 })), + }; + const run = ( + operations: InitialConnectionOperations, + isMobileWallet = false + ) => + initialize(operations, { + hasExternalProvider: false, + isMobileWallet, + queryParamsInitChainId: 2, + wagmiConfig, + }); + + const reconnectFailure = vi.fn(() => Effect.fail(operationFailure(cause))); + const reconnectConnect = vi.fn(() => + Effect.succeed({ accounts: [], chainId: 1 }) + ); + const reconnectSwitch = vi.fn(() => Effect.succeed({ id: 2 })); + await run( + { + ...baseOperations, + connect: reconnectConnect, + reconnect: reconnectFailure, + switchChain: reconnectSwitch, + }, + true + ); + const fallbackConnectFailure = vi.fn(() => + Effect.fail(operationFailure(cause)) + ); + await run( + { + ...baseOperations, + connect: fallbackConnectFailure, + reconnect: vi.fn(() => Effect.succeed([])), + }, + true + ); + const switchFailure = vi.fn(() => Effect.fail(operationFailure(cause))); + await run({ + ...baseOperations, + switchChain: switchFailure, + }); + + expect(reconnectFailure).toHaveBeenCalledOnce(); + expect(reconnectSwitch).toHaveBeenCalledOnce(); + expect(reconnectConnect).toHaveBeenCalledOnce(); + expect(reconnectConnect).toHaveBeenCalledWith(wagmiConfig, { + chainId: 2, + connector: injectedConnector, + }); + expect(fallbackConnectFailure).toHaveBeenCalledOnce(); + expect(switchFailure).toHaveBeenCalledOnce(); + }); + + it("retains configured connectors and manual connect after initial switching fails", async () => { + const configuredConnector = { id: "configured" } as Connector; + const wagmiConfig = { + connectors: [configuredConnector], + state: { chainId: 1 }, + } as unknown as ReturnType; + const connect = vi.fn(() => Effect.succeed({ accounts: [], chainId: 1 })); + const operations: InitialConnectionOperations = { + connect, + reconnect: vi.fn(() => Effect.succeed([{} as never])), + switchChain: vi.fn(() => + Effect.fail(operationFailure(new Error("switch rejected"))) + ), + }; + + await Effect.runPromise( + (await makeInitializer(operations))({ + hasExternalProvider: false, + isLedgerDappBrowser: false, + isMobileWallet: false, + queryParamsInitChainId: 2, + wagmiConfig, + }) + ); + + expect(wagmiConfig.connectors).toEqual([configuredConnector]); + + await Effect.runPromise( + operations.connect(wagmiConfig, { connector: configuredConnector }) + ); + expect(connect).toHaveBeenCalledOnce(); + }); + + it("keeps wallet configuration construction failures fatal", async () => { + const cause = new Error("connector construction failed"); + await expect( + Effect.runPromise( + Effect.scoped( + buildWagmiConfig({ + chainIconMapping: undefined, + customConnectors: () => { + throw cause; + }, + disableInjectedProviderDiscovery: true, + enabledNetworks: new Set(["ethereum"]), + forceWalletConnectOnly: false, + institutionalWallets: false, + isLedgerLive: false, + isSafe: false, + mapWalletFn: undefined, + mapWalletListFn: undefined, + persistPublicKey: () => Effect.void, + queryParams: Schema.decodeSync(InitParams)(emptyInitParams), + solanaConnection: {} as SolanaConnection, + solanaWallets: [], + tonConnectManifestUrl: undefined, + variant: "default", + }).pipe(Effect.provide(WagmiOperations.layer)) + ) + ) + ).rejects.toThrow(cause.message); + }); + + it("disposes MIPD ownership and ignores callbacks from the released scope", async () => { + const publish = vi.fn(); + const unsubscribe = vi.fn(); + let publishAfterRelease: (() => void) | undefined; + + await Effect.runPromise( + Effect.scoped( + scopedMipdSubscription({ + initialProviders: [], + publish, + subscribe: (onProviders) => { + publishAfterRelease = () => onProviders([]); + return unsubscribe; + }, + }) + ) + ); + + expect(publish).toHaveBeenCalledOnce(); + expect(unsubscribe).toHaveBeenCalledOnce(); + + publishAfterRelease?.(); + expect(publish).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/widget/tests/providers/wallet-service-projections.test.ts b/packages/widget/tests/providers/wallet-service-projections.test.ts new file mode 100644 index 000000000..922ae5869 --- /dev/null +++ b/packages/widget/tests/providers/wallet-service-projections.test.ts @@ -0,0 +1,63 @@ +import { Effect, Layer, Option, Stream } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import { + currentWalletConfigResultAtom, + currentWalletLedgerStateAtom, + currentWalletStateResultAtom, +} from "../../src/features/wallet/state/root-atom"; +import { makeDefaultConfig } from "../../src/services/wallet/default-wagmi-config"; +import { + disconnectedLedgerConnectorState, + disconnectedNormalizedWalletState, + type WalletState, +} from "../../src/services/wallet/domain/state"; +import { WalletService } from "../../src/services/wallet/wallet-service"; + +describe("WalletService projections", () => { + it("projects one cohesive Wallet State and stable Wagmi config", async () => { + const wagmiConfig = makeDefaultConfig(); + const walletState = { + connection: disconnectedNormalizedWalletState, + ledger: disconnectedLedgerConnectorState, + } satisfies WalletState; + const registry = AtomRegistry.make({ + initialValues: [ + [ + walletRuntime.layer, + Layer.succeed(WalletService, { + state: Effect.succeed(walletState), + states: Stream.succeed(walletState), + wagmiConfig, + } as never) as never, + ], + ], + }); + + registry.mount(currentWalletConfigResultAtom); + registry.mount(currentWalletStateResultAtom); + registry.mount(currentWalletLedgerStateAtom); + + await vi.waitFor(() => { + expect( + Option.getOrThrow( + AsyncResult.value(registry.get(currentWalletConfigResultAtom)) + ) + ).toBe(wagmiConfig); + expect( + Option.getOrThrow( + AsyncResult.value(registry.get(currentWalletStateResultAtom)) + ) + ).toBe(walletState.connection); + expect( + Option.getOrThrow( + AsyncResult.value(registry.get(currentWalletLedgerStateAtom)) + ) + ).toBe(walletState.ledger); + }); + + registry.dispose(); + }); +}); diff --git a/packages/widget/tests/providers/wallet-state-atom.test.ts b/packages/widget/tests/providers/wallet-state-atom.test.ts new file mode 100644 index 000000000..29d2b85ee --- /dev/null +++ b/packages/widget/tests/providers/wallet-state-atom.test.ts @@ -0,0 +1,152 @@ +import type { Account } from "@ledgerhq/wallet-api-client"; +import { mainnet, optimism } from "viem/chains"; +import { describe, expect, it } from "vitest"; +import type { Connector } from "wagmi"; +import { evmChainsMap } from "../../src/domain/types/chains/evm"; +import type { WalletCoreState } from "../../src/services/wallet/domain/state"; +import { disconnectedLedgerConnectorState } from "../../src/services/wallet/domain/state"; +import { + normalizeWalletState, + type WalletStateController, +} from "../../src/services/wallet/state-projection"; + +type WalletConnectionSnapshot = WalletCoreState["connection"]; + +const disconnectedWalletConnection: WalletConnectionSnapshot = { + address: undefined, + addresses: undefined, + chain: undefined, + chainId: undefined, + connector: undefined, + isConnected: false, + isConnecting: false, + isDisconnected: true, + isReconnecting: false, + status: "disconnected", +}; + +const address = "0x0000000000000000000000000000000000000001"; +const forceAddress = "0x0000000000000000000000000000000000000002"; +const connector = { id: "mock" } as Connector; +const controller: WalletStateController = { + cosmosConfig: { cosmosChainsMap: {} }, + evmConfig: { evmChainsMap: { ethereum: evmChainsMap.ethereum } }, + isLedgerLive: false, + miscConfig: { miscChainsMap: {} }, + substrateConfig: { substrateChainsMap: {} }, +}; +const connected = { + ...disconnectedWalletConnection, + address, + addresses: [address], + chain: mainnet, + chainId: mainnet.id, + connector, + isConnected: true, + isDisconnected: false, + status: "connected", +} as WalletConnectionSnapshot; + +const normalize = ( + connection: WalletConnectionSnapshot, + overrides: Partial[0]> = {} +) => + normalizeWalletState({ + additionalAddresses: null, + connection, + connectorChains: [mainnet], + controller, + forceAddress: undefined, + ledgerState: disconnectedLedgerConnectorState, + ...overrides, + }); + +describe("normalized wallet state atom", () => { + it("distinguishes disconnected and connecting core states", () => { + expect(normalize(disconnectedWalletConnection)).toMatchObject({ + address: null, + network: null, + status: "disconnected", + }); + expect( + normalize({ + ...disconnectedWalletConnection, + isConnecting: true, + isDisconnected: false, + status: "connecting", + } as WalletConnectionSnapshot) + ).toMatchObject({ status: "connecting" }); + expect( + normalize({ + ...disconnectedWalletConnection, + isDisconnected: false, + isReconnecting: true, + status: "reconnecting", + } as WalletConnectionSnapshot) + ).toMatchObject({ status: "connecting" }); + }); + + it("publishes unsupported state when the connected chain has no widget mapping", () => { + expect( + normalize({ + ...connected, + chain: optimism, + chainId: optimism.id, + } as WalletConnectionSnapshot) + ).toMatchObject({ + address, + chain: optimism, + connector, + network: null, + status: "unsupported", + }); + }); + + it("normalizes supported state, force address, chains, and auxiliary data", () => { + const account = { id: "ledger-account" } as Account; + const additionalAddresses = { cosmosPubKey: "A".repeat(44) }; + const state = normalize(connected, { + additionalAddresses, + forceAddress, + ledgerState: { + accounts: [account], + currentAccountId: account.id, + disabledChains: [], + }, + }); + + expect(state).toMatchObject({ + additionalAddresses, + address: forceAddress, + chain: mainnet, + connector, + connectorChains: [mainnet], + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [account], + network: "ethereum", + status: "connected", + }); + }); + + it("recognizes Ledger mode and its no-account placeholder", () => { + const ledgerConnector = { + id: "ledgerLive", + noAccountPlaceholder: "N/A", + } as unknown as Connector; + const state = normalize( + { + ...connected, + address: "N/A", + addresses: ["N/A"], + connector: ledgerConnector, + } as unknown as WalletConnectionSnapshot, + { controller: { ...controller, isLedgerLive: true } } + ); + + expect(state).toMatchObject({ + isLedgerLive: true, + isLedgerLiveAccountPlaceholder: true, + status: "connected", + }); + }); +}); diff --git a/packages/widget/tests/providers/wallet/cosmos-substrate-wallet-drivers.test.ts b/packages/widget/tests/providers/wallet/cosmos-substrate-wallet-drivers.test.ts new file mode 100644 index 000000000..dd4f09cdb --- /dev/null +++ b/packages/widget/tests/providers/wallet/cosmos-substrate-wallet-drivers.test.ts @@ -0,0 +1,115 @@ +import type { ChainWalletBase } from "@cosmos-kit/core"; +import { Effect } from "effect"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { makeCosmosWalletDriver } from "../../../src/services/wallet/drivers/cosmos"; +import { makeSubstrateWalletDriver } from "../../../src/services/wallet/drivers/substrate"; + +const substrateTx = JSON.stringify({ + metadataRpc: "0x00", + specName: "polkadot", + specVersion: 1, + tx: { + address: "address", + blockHash: "0x00", + blockNumber: "0x01", + era: "0x00", + genesisHash: "0x00", + metadataRpc: "0x00", + method: "0x00", + nonce: "0x00", + signedExtensions: ["CheckNonce"], + specVersion: "0x01", + tip: "0x00", + transactionVersion: "0x01", + version: 4, + }, +}); + +describe("Cosmos and Substrate wallet drivers", () => { + it("signs Cosmos transactions as non-broadcast payloads", async () => { + const signTransaction = vi.fn(() => Effect.succeed("cosmos-signed")); + const connector = { + id: "cosmos", + type: "cosmosProvider", + signTransaction, + } as unknown as Connector; + const chainWallet = { chainId: "cosmoshub-4" } as ChainWalletBase; + + await expect( + Effect.runPromise( + makeCosmosWalletDriver({ chainWallet, connector }).signTransaction({ + tx: "abcd", + }) + ) + ).resolves.toEqual({ broadcasted: false, signedTx: "cosmos-signed" }); + expect(signTransaction).toHaveBeenCalledWith({ + cw: chainWallet, + tx: "abcd", + }); + }); + + it("requires the Cosmos chain wallet capability", async () => { + const connector = { + id: "cosmos", + type: "cosmosProvider", + signTransaction: vi.fn(), + } as unknown as Connector; + const failure = await Effect.runPromise( + Effect.flip( + makeCosmosWalletDriver({ + chainWallet: null, + connector, + }).signTransaction({ tx: "abcd" }) + ) + ); + + expect(failure._tag).toBe("WalletCapabilityUnavailableError"); + }); + + it("decodes and signs Substrate transactions without broadcasting", async () => { + const signTransaction = vi.fn(() => Effect.succeed("substrate-signed")); + const connector = { + id: "substrate", + type: "substrateProvider", + signTransaction, + } as unknown as Connector; + + await expect( + Effect.runPromise( + makeSubstrateWalletDriver({ connector }).signTransaction({ + tx: substrateTx, + }) + ) + ).resolves.toEqual({ + broadcasted: false, + signedTx: "substrate-signed", + }); + expect(signTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + rawTx: substrateTx, + tx: expect.objectContaining({ signedExtensions: ["CheckNonce"] }), + }) + ); + }); + + it("keeps Substrate decoding and signing failures distinct", async () => { + const cause = new Error("rejected"); + const connector = { + id: "substrate", + type: "substrateProvider", + signTransaction: () => Effect.fail(cause), + } as unknown as Connector; + const driver = makeSubstrateWalletDriver({ connector }); + + const decodeFailure = await Effect.runPromise( + Effect.flip(driver.signTransaction({ tx: "{}" })) + ); + const signingFailure = await Effect.runPromise( + Effect.flip(driver.signTransaction({ tx: substrateTx })) + ); + + expect(decodeFailure._tag).toBe("WalletDecodeError"); + expect(signingFailure._tag).toBe("WalletSigningError"); + }); +}); diff --git a/packages/widget/tests/providers/wallet/evm-wallet-driver.test.ts b/packages/widget/tests/providers/wallet/evm-wallet-driver.test.ts new file mode 100644 index 000000000..48cd8a3e3 --- /dev/null +++ b/packages/widget/tests/providers/wallet/evm-wallet-driver.test.ts @@ -0,0 +1,96 @@ +import { Effect } from "effect"; +import type { Hash } from "viem"; +import { zeroAddress } from "viem"; +import { describe, expect, it, vi } from "vitest"; +import { WalletBroadcastError } from "../../../src/services/wallet/domain/errors"; +import { + decodeEvmTransaction, + makeEvmWalletDriver, +} from "../../../src/services/wallet/drivers/evm"; + +const transaction = (fees: object) => + JSON.stringify({ + chainId: 1, + data: "0x1234", + from: zeroAddress, + gasLimit: "21000", + nonce: 1, + to: zeroAddress, + type: 2, + value: "12", + ...fees, + }); + +describe("EVM wallet driver", () => { + it("prepares a legacy request and preserves its gas price", async () => { + await expect( + Effect.runPromise(decodeEvmTransaction(transaction({ gasPrice: "7" }))) + ).resolves.toEqual({ + chainId: 1, + data: "0x1234", + gas: 21_000n, + gasPrice: 7n, + to: zeroAddress, + type: "legacy", + value: 12n, + }); + }); + + it("prepares an EIP-1559 request and preserves both fee fields", async () => { + await expect( + Effect.runPromise( + decodeEvmTransaction( + transaction({ maxFeePerGas: "9", maxPriorityFeePerGas: "2" }) + ) + ) + ).resolves.toEqual({ + chainId: 1, + data: "0x1234", + gas: 21_000n, + maxFeePerGas: 9n, + maxPriorityFeePerGas: 2n, + to: zeroAddress, + type: "eip1559", + value: 12n, + }); + }); + + it("returns a broadcast result from the core send command", async () => { + const hash = `0x${"1".repeat(64)}` as Hash; + const sendTransaction = vi.fn(() => + Effect.succeed({ broadcasted: true as const, signedTx: hash }) + ); + const driver = makeEvmWalletDriver({ sendTransaction }); + + await expect( + Effect.runPromise( + driver.signTransaction({ tx: transaction({ gasPrice: "7" }) }) + ) + ).resolves.toEqual({ broadcasted: true, signedTx: hash }); + expect(sendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ gasPrice: 7n, type: "legacy" }) + ); + }); + + it("keeps decoding and broadcasting failures distinct", async () => { + const cause = new Error("provider rejected"); + const driver = makeEvmWalletDriver({ + sendTransaction: () => + Effect.fail(new WalletBroadcastError({ cause, customMessage: null })), + }); + + const decodeFailure = await Effect.runPromise( + Effect.flip(driver.signTransaction({ tx: "not-json" })) + ); + const broadcastFailure = await Effect.runPromise( + Effect.flip( + driver.signTransaction({ tx: transaction({ gasPrice: "7" }) }) + ) + ); + + expect(decodeFailure._tag).toBe("WalletDecodeError"); + expect(broadcastFailure).toEqual( + new WalletBroadcastError({ cause, customMessage: null }) + ); + }); +}); diff --git a/packages/widget/tests/providers/wallet/external-provider-sync.test.ts b/packages/widget/tests/providers/wallet/external-provider-sync.test.ts new file mode 100644 index 000000000..a6482c486 --- /dev/null +++ b/packages/widget/tests/providers/wallet/external-provider-sync.test.ts @@ -0,0 +1,267 @@ +import { Deferred, Effect, Stream, SubscriptionRef } from "effect"; +import { mainnet } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { normalizeWidgetConfig } from "../../../src/app/config/settings"; +import type { SKExternalProviders } from "../../../src/public-api/types"; +import { + normalizeWidgetBootstrapConfig, + WidgetConfigService, +} from "../../../src/services/config/widget-config"; +import { + makeExternalProviderSnapshot, + type WalletBootstrapResult, +} from "../../../src/services/wallet/bootstrap"; +import { makeDefaultConfig } from "../../../src/services/wallet/default-wagmi-config"; +import type { WalletCoreState } from "../../../src/services/wallet/domain/state"; +import { disconnectedNormalizedWalletState } from "../../../src/services/wallet/domain/state"; +import { installExternalProviderSynchronization } from "../../../src/services/wallet/external-provider-sync"; +import type { + WalletStateContext, + WalletStateRuntime, +} from "../../../src/services/wallet/wallet-state"; +import { makeWalletTestController } from "./wallet-test-controller"; + +const firstAddress = "0x0000000000000000000000000000000000000001"; +const secondAddress = "0x0000000000000000000000000000000000000002"; + +const externalProviders = ( + overrides: Partial = {} +): SKExternalProviders => ({ + currentAddress: firstAddress, + currentChain: 1, + provider: { + sendTransaction: async () => "transaction-hash", + signMessage: async () => "signature", + switchChain: async () => undefined, + }, + supportedChainIds: [1], + type: "generic", + ...overrides, +}); + +const disconnectedCore = { + address: undefined, + addresses: undefined, + chain: undefined, + chainId: undefined, + connector: undefined, + isConnected: false, + isConnecting: false, + isDisconnected: true, + isReconnecting: false, + status: "disconnected", +} as const; + +const makeContext = ( + connector: Connector | undefined, + connected: boolean +): WalletStateContext => ({ + core: { + connection: connected + ? ({ + address: firstAddress, + addresses: [firstAddress], + chain: mainnet, + chainId: mainnet.id, + connector, + isConnected: true, + isConnecting: false, + isDisconnected: false, + isReconnecting: false, + status: "connected", + } as WalletCoreState["connection"]) + : disconnectedCore, + connectors: connector ? [connector] : [], + }, + routing: {} as never, + state: { + connection: disconnectedNormalizedWalletState, + ledger: { + accounts: [], + currentAccountId: undefined, + disabledChains: [], + }, + }, +}); + +const makeHarness = async ({ + connect = () => Effect.void, + connector, + connected, +}: { + readonly connect?: () => Effect.Effect; + readonly connector: Connector | undefined; + readonly connected: boolean; +}) => { + const initial = normalizeWidgetConfig({ + apiKey: "api-key", + externalProviders: externalProviders(), + variant: "default", + }); + const config = await Effect.runPromise(SubscriptionRef.make(initial)); + const invariant = await Effect.runPromise(Deferred.make()); + const context = makeContext(connector, connected); + const state = { + context: Effect.succeed(context), + contexts: Stream.concat(Stream.succeed(context), Stream.never), + failInvariant: (error) => + Deferred.succeed(invariant, error).pipe(Effect.asVoid), + } satisfies WalletStateRuntime; + const wagmiConfig = makeDefaultConfig(); + const controller = makeWalletTestController({ + actions: { connect }, + queryParamsInitChainId: undefined, + wagmiConfig, + }); + const snapshot = makeExternalProviderSnapshot(initial)!; + const bootstrap = { + controller, + core: { + current: Effect.succeed(context.core), + states: Stream.never, + }, + externalProviderMode: true, + externalProviders: { current: snapshot }, + snapshot: { + browser: { + href: "https://widget.test/", + isLedgerDappBrowser: false, + isMobileWallet: false, + }, + config: normalizeWidgetBootstrapConfig({ + isLedgerLive: initial.isLedgerLive, + settings: initial, + }), + enabledNetworks: new Set(["ethereum"]), + externalProviders: { current: snapshot }, + initParams: {} as never, + }, + } satisfies WalletBootstrapResult; + const configLayer = WidgetConfigService.layer({ + changes: SubscriptionRef.changes(config), + current: SubscriptionRef.get(config), + initial, + }); + return { bootstrap, config, configLayer, invariant, state }; +}; + +describe("external-provider synchronization", () => { + it("updates live provider values and sends connector notifications", async () => { + const accountsChanged = vi.fn(); + const chainChanged = vi.fn(); + const supportedChanged = vi.fn(); + const notified = await Effect.runPromise(Deferred.make()); + const connector = { + id: "externalProviderConnector", + name: "External", + onAccountsChanged: (accounts: readonly string[]) => { + accountsChanged(accounts); + void Effect.runPromise(Deferred.succeed(notified, undefined)); + }, + onChainChanged: chainChanged, + onSupportedChainsChanged: supportedChanged, + type: "externalProvider", + uid: "external-uid", + } as unknown as Connector; + const harness = await makeHarness({ connector, connected: true }); + const nextProviders = externalProviders({ + currentAddress: secondAddress, + currentChain: 10, + supportedChainIds: [1, 10], + }); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* installExternalProviderSynchronization(harness); + yield* SubscriptionRef.set( + harness.config, + normalizeWidgetConfig({ + apiKey: "api-key", + externalProviders: nextProviders, + variant: "default", + }) + ); + yield* Deferred.await(notified); + yield* Effect.yieldNow; + }).pipe(Effect.provide(harness.configLayer)) + ) + ); + + expect(harness.bootstrap.externalProviders?.current).toEqual( + makeExternalProviderSnapshot( + normalizeWidgetConfig({ + apiKey: "api-key", + externalProviders: nextProviders, + variant: "default", + }) + ) + ); + expect(accountsChanged).toHaveBeenCalledWith([secondAddress]); + expect(chainChanged).toHaveBeenCalledWith("10"); + expect(supportedChanged).toHaveBeenCalledWith({ + currentChainId: 10, + supportedChainIds: [1, 10], + }); + }); + + it("does not start a duplicate automatic connection while one is pending", async () => { + const started = await Effect.runPromise(Deferred.make()); + const release = await Effect.runPromise(Deferred.make()); + const connect = vi.fn(() => + Effect.gen(function* () { + yield* Deferred.succeed(started, undefined); + yield* Deferred.await(release); + }) + ); + const connector = { + id: "externalProviderConnector", + name: "External", + onAccountsChanged: () => undefined, + onChainChanged: () => undefined, + onSupportedChainsChanged: () => undefined, + type: "externalProvider", + uid: "external-uid", + } as unknown as Connector; + const harness = await makeHarness({ connect, connector, connected: false }); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* installExternalProviderSynchronization(harness); + yield* Deferred.await(started); + const current = yield* SubscriptionRef.get(harness.config); + yield* SubscriptionRef.set(harness.config, { + ...current, + externalProviders: externalProviders({ currentChain: 10 }), + }); + yield* Effect.yieldNow; + expect(connect).toHaveBeenCalledOnce(); + yield* Deferred.succeed(release, undefined); + }).pipe(Effect.provide(harness.configLayer)) + ) + ); + }); + + it("fails the runtime when the fixed external connector is missing", async () => { + const harness = await makeHarness({ + connector: undefined, + connected: false, + }); + + const failure = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* installExternalProviderSynchronization(harness); + return yield* Deferred.await(harness.invariant); + }).pipe(Effect.provide(harness.configLayer)) + ) + ); + + expect(failure).toMatchObject({ + _tag: "WalletRuntimeInvariantError", + reason: "external-provider-connector-missing", + }); + }); +}); diff --git a/packages/widget/tests/providers/wallet/external-provider-wallet-driver.test.ts b/packages/widget/tests/providers/wallet/external-provider-wallet-driver.test.ts new file mode 100644 index 000000000..e44e11e2d --- /dev/null +++ b/packages/widget/tests/providers/wallet/external-provider-wallet-driver.test.ts @@ -0,0 +1,114 @@ +import { Effect } from "effect"; +import { zeroAddress } from "viem"; +import { describe, expect, it } from "vitest"; +import type { Connector } from "wagmi"; +import { ExternalProviderError } from "../../../src/domain/types/external-providers"; +import { makeExternalProviderWalletDriver } from "../../../src/services/wallet/drivers/external-provider"; + +const tx = JSON.stringify({ + chainId: 1, + data: "0x1234", + from: zeroAddress, + gasLimit: "21000", + gasPrice: "1", + nonce: 1, + to: zeroAddress, + type: 0, +}); + +const txMeta = {} as Parameters< + ReturnType["signTransaction"] +>[0]["txMeta"]; + +describe("external-provider wallet driver", () => { + it("reads fresh host callbacks and preserves their Promise contract", async () => { + const callbacks: { + signMessage: (message: string) => Promise; + sendTransaction: (input: unknown, meta: unknown) => Promise; + } = { + signMessage: async () => "first-message", + sendTransaction: async () => "first-hash", + }; + const connector = { + id: "externalProviderConnector", + signMessage: (message: string) => + Effect.tryPromise(() => callbacks.signMessage(message)), + sendTransaction: (input: unknown, meta: unknown) => + Effect.tryPromise(() => callbacks.sendTransaction(input, meta)), + } as unknown as Connector; + const driver = makeExternalProviderWalletDriver({ connector }); + + await expect( + Effect.runPromise(driver.signMessage({ message: "message" })) + ).resolves.toBe("first-message"); + + callbacks.signMessage = async () => "second-message"; + callbacks.sendTransaction = async () => "second-hash"; + + await expect( + Effect.runPromise(driver.signMessage({ message: "message" })) + ).resolves.toBe("second-message"); + await expect( + Effect.runPromise( + driver.signTransaction({ + address: zeroAddress, + network: "ethereum", + tx, + txMeta, + }) + ) + ).resolves.toEqual({ broadcasted: true, signedTx: "second-hash" }); + }); + + it("maps host custom errors to the broadcast failure", async () => { + const connector = { + id: "externalProviderConnector", + sendTransaction: () => + Effect.fail( + new ExternalProviderError({ + customMessage: "Open your host wallet", + message: "Open your host wallet", + }) + ), + } as unknown as Connector; + const failure = await Effect.runPromise( + Effect.flip( + makeExternalProviderWalletDriver({ connector }).signTransaction({ + address: zeroAddress, + network: "ethereum", + tx, + txMeta, + }) + ) + ); + + expect(failure._tag).toBe("WalletBroadcastError"); + if (failure._tag === "WalletBroadcastError") { + expect(failure.customMessage).toBe("Open your host wallet"); + } + }); + + it("fails invalid host transaction payloads before invoking the callback", async () => { + let calls = 0; + const connector = { + id: "externalProviderConnector", + sendTransaction: () => { + calls += 1; + return Effect.succeed("hash"); + }, + } as unknown as Connector; + const failure = await Effect.runPromise( + Effect.flip( + makeExternalProviderWalletDriver({ connector }).signTransaction({ + address: zeroAddress, + network: "ethereum", + tx: "{}", + txMeta, + }) + ) + ); + + expect(failure._tag).toBe("WalletDecodeError"); + expect(calls).toBe(0); + }); +}); diff --git a/packages/widget/tests/providers/wallet/ledger-wallet-driver.test.ts b/packages/widget/tests/providers/wallet/ledger-wallet-driver.test.ts new file mode 100644 index 000000000..9d981aacd --- /dev/null +++ b/packages/widget/tests/providers/wallet/ledger-wallet-driver.test.ts @@ -0,0 +1,126 @@ +import type { Account } from "@ledgerhq/wallet-api-client"; +import type { RawTransaction } from "@ledgerhq/wallet-api-core"; +import { Effect, Result } from "effect"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { makeLedgerWalletDriver } from "../../../src/services/wallet/drivers/ledger"; + +const account = { id: "ledger-account" } as Account; +const transactionInput = { + ledgerHwAppId: "ethereum", + network: "ethereum" as const, + tx: "{}", + txMeta: {} as Parameters< + ReturnType["signTransaction"] + >[0]["txMeta"], +}; + +const makeConnector = () => { + const prepareTransaction = vi.fn<() => Result.Result>( + () => Result.succeed({} as RawTransaction) + ); + const deserializeTransaction = vi.fn(() => ({ family: "ethereum" })); + const signAndBroadcast = vi.fn(async () => "0xledger-hash"); + const switchAccount = vi.fn(); + const connector = { + id: "ledgerLive", + prepareTransaction, + deserializeTransaction, + walletApiClient: { transaction: { signAndBroadcast } }, + switchAccount, + } as unknown as Connector; + + return { + connector, + deserializeTransaction, + prepareTransaction, + signAndBroadcast, + switchAccount, + }; +}; + +describe("Ledger wallet driver", () => { + it("prepares, deserializes, selects the account, and broadcasts", async () => { + const ledger = makeConnector(); + const driver = makeLedgerWalletDriver({ + connector: ledger.connector, + currentAccountId: account.id, + }); + + await expect( + Effect.runPromise(driver.signTransaction(transactionInput)) + ).resolves.toEqual({ + broadcasted: true, + signedTx: "0xledger-hash", + }); + expect(ledger.prepareTransaction).toHaveBeenCalledWith({ + network: "ethereum", + tx: "{}", + txMeta: transactionInput.txMeta, + }); + expect(ledger.deserializeTransaction).toHaveBeenCalledTimes(1); + expect(ledger.signAndBroadcast).toHaveBeenCalledWith( + account.id, + expect.anything(), + { hwAppId: "ethereum" } + ); + }); + + it("requires a selected account before preparing", async () => { + const ledger = makeConnector(); + const failure = await Effect.runPromise( + Effect.flip( + makeLedgerWalletDriver({ + connector: ledger.connector, + currentAccountId: undefined, + }).signTransaction(transactionInput) + ) + ); + + expect(failure._tag).toBe("WalletCapabilityUnavailableError"); + expect(ledger.prepareTransaction).not.toHaveBeenCalled(); + }); + + it("maps preparation and broadcast failures to distinct errors", async () => { + const ledger = makeConnector(); + ledger.prepareTransaction.mockReturnValue(Result.fail("invalid tx")); + const decodeFailure = await Effect.runPromise( + Effect.flip( + makeLedgerWalletDriver({ + connector: ledger.connector, + currentAccountId: account.id, + }).signTransaction(transactionInput) + ) + ); + + ledger.prepareTransaction.mockReturnValue( + Result.succeed({} as RawTransaction) + ); + ledger.signAndBroadcast.mockRejectedValue(new Error("rejected")); + const broadcastFailure = await Effect.runPromise( + Effect.flip( + makeLedgerWalletDriver({ + connector: ledger.connector, + currentAccountId: account.id, + }).signTransaction(transactionInput) + ) + ); + + expect(decodeFailure._tag).toBe("WalletDecodeError"); + expect(broadcastFailure._tag).toBe("WalletBroadcastError"); + }); + + it("switches the Ledger account through the driver", async () => { + const ledger = makeConnector(); + const driver = makeLedgerWalletDriver({ + connector: ledger.connector, + currentAccountId: account.id, + }); + + await Effect.runPromise( + driver.switchAccount({ account, connector: ledger.connector }) + ); + + expect(ledger.switchAccount).toHaveBeenCalledWith(account); + }); +}); diff --git a/packages/widget/tests/providers/wallet/misc-wallet-drivers.test.ts b/packages/widget/tests/providers/wallet/misc-wallet-drivers.test.ts new file mode 100644 index 000000000..4ba77ee5d --- /dev/null +++ b/packages/widget/tests/providers/wallet/misc-wallet-drivers.test.ts @@ -0,0 +1,127 @@ +import { Effect } from "effect"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { + makeCardanoWalletDriver, + makeSolanaWalletDriver, + makeTonWalletDriver, + makeTronWalletDriver, +} from "../../../src/services/wallet/drivers/misc"; + +const tronTx = JSON.stringify({ + raw_data: { + contract: [], + expiration: 1, + ref_block_bytes: "00", + ref_block_hash: "00", + timestamp: 1, + }, + raw_data_hex: "00", + txID: "tron-id", + visible: true, +}); + +describe("miscellaneous wallet drivers", () => { + it("normalizes a Tron signed transaction to a JSON payload", async () => { + const signTransaction = vi.fn(async () => ({ txID: "signed" })); + const connector = { + id: "tronLink", + signTransaction, + } as unknown as Connector; + + await expect( + Effect.runPromise( + makeTronWalletDriver({ connector }).signTransaction({ tx: tronTx }) + ) + ).resolves.toEqual({ + broadcasted: false, + signedTx: JSON.stringify({ txID: "signed" }), + }); + expect(signTransaction).toHaveBeenCalledWith( + expect.objectContaining({ txID: "tron-id" }) + ); + }); + + it("preserves Solana's broadcast result", async () => { + const sendTransaction = vi.fn(async () => "solana-hash"); + const connector = { + id: "solana", + isSolanaConnector: true, + sendTransaction, + } as unknown as Connector; + + await expect( + Effect.runPromise( + makeSolanaWalletDriver({ connector }).signTransaction({ tx: "abcd" }) + ) + ).resolves.toEqual({ + broadcasted: true, + signedTx: "solana-hash", + }); + expect(sendTransaction).toHaveBeenCalledWith("abcd"); + }); + + it("preserves Cardano's signed-payload result", async () => { + const signTransaction = vi.fn(() => Effect.succeed("cardano-signed")); + const connector = { + id: "cardano", + type: "cardanoWallet", + signTransaction, + } as unknown as Connector; + + await expect( + Effect.runPromise( + makeCardanoWalletDriver({ connector }).signTransaction({ tx: "abcd" }) + ) + ).resolves.toEqual({ + broadcasted: false, + signedTx: "cardano-signed", + }); + }); + + it("preserves TON's broadcast result", async () => { + const signTransaction = vi.fn(() => Effect.succeed("ton-hash")); + const connector = { + id: "ton", + type: "tonWallet", + signTransaction, + } as unknown as Connector; + + await expect( + Effect.runPromise( + makeTonWalletDriver({ connector }).signTransaction({ tx: "{}" }) + ) + ).resolves.toEqual({ broadcasted: true, signedTx: "ton-hash" }); + }); + + it("maps decoding and connector failures to typed driver errors", async () => { + const rejected = new Error("rejected"); + const tronConnector = { + id: "tronLink", + signTransaction: vi.fn(), + } as unknown as Connector; + const tonConnector = { + id: "ton", + type: "tonWallet", + signTransaction: () => Effect.fail(rejected), + } as unknown as Connector; + + const decodeFailure = await Effect.runPromise( + Effect.flip( + makeTronWalletDriver({ connector: tronConnector }).signTransaction({ + tx: "{}", + }) + ) + ); + const broadcastFailure = await Effect.runPromise( + Effect.flip( + makeTonWalletDriver({ connector: tonConnector }).signTransaction({ + tx: "{}", + }) + ) + ); + + expect(decodeFailure._tag).toBe("WalletDecodeError"); + expect(broadcastFailure._tag).toBe("WalletBroadcastError"); + }); +}); diff --git a/packages/widget/tests/providers/wallet/safe-wallet-driver.test.ts b/packages/widget/tests/providers/wallet/safe-wallet-driver.test.ts new file mode 100644 index 000000000..e9fa362d8 --- /dev/null +++ b/packages/widget/tests/providers/wallet/safe-wallet-driver.test.ts @@ -0,0 +1,103 @@ +import { Effect, Fiber, Schedule } from "effect"; +import { zeroAddress } from "viem"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { makeSafeWalletDriver } from "../../../src/services/wallet/drivers/safe"; + +const tx = JSON.stringify({ + chainId: 1, + data: "0x1234", + from: zeroAddress, + gasLimit: "21000", + gasPrice: "1", + nonce: 1, + to: zeroAddress, + type: 0, +}); + +const statuses = { + AWAITING_CONFIRMATIONS: "AWAITING_CONFIRMATIONS", + CANCELLED: "CANCELLED", + FAILED: "FAILED", + SUCCESS: "SUCCESS", +}; + +const makeConnector = ( + getTxStatus: () => Effect.Effect< + { readonly txHash: string | null; readonly txStatus: string }, + Error + > +) => + ({ + getTxStatus, + id: "safe", + sendTransactions: vi.fn(() => Effect.succeed({ safeTxHash: "safe-hash" })), + txStatus: statuses, + }) as unknown as Connector; + +describe("Safe wallet driver", () => { + it("submits and polls until the Safe transaction succeeds", async () => { + let attempts = 0; + const connector = makeConnector(() => { + attempts += 1; + return Effect.succeed( + attempts === 1 + ? { txHash: null, txStatus: statuses.AWAITING_CONFIRMATIONS } + : { txHash: "0xsafe-hash", txStatus: statuses.SUCCESS } + ); + }); + const driver = makeSafeWalletDriver({ + confirmationRetries: 2, + confirmationSchedule: Schedule.spaced("1 millis"), + connector, + }); + + await expect( + Effect.runPromise(driver.signTransaction({ address: zeroAddress, tx })) + ).resolves.toEqual({ + broadcasted: true, + signedTx: "0xsafe-hash", + }); + expect(attempts).toBe(2); + }); + + it("does not retry a terminal Safe failure", async () => { + const getTxStatus = vi.fn(() => + Effect.succeed({ txHash: null, txStatus: statuses.FAILED }) + ); + const failure = await Effect.runPromise( + Effect.flip( + makeSafeWalletDriver({ + confirmationRetries: 2, + confirmationSchedule: Schedule.spaced("1 millis"), + connector: makeConnector(getTxStatus), + }).signTransaction({ address: zeroAddress, tx }) + ) + ); + + expect(failure._tag).toBe("WalletBroadcastError"); + expect(getTxStatus).toHaveBeenCalledTimes(1); + }); + + it("interrupts confirmation polling without a React unmount check", async () => { + const getTxStatus = vi.fn(() => + Effect.succeed({ + txHash: null, + txStatus: statuses.AWAITING_CONFIRMATIONS, + }) + ); + const fiber = Effect.runFork( + makeSafeWalletDriver({ + confirmationRetries: 120, + confirmationSchedule: Schedule.spaced("1 hour"), + connector: makeConnector(getTxStatus), + }).signTransaction({ address: zeroAddress, tx }) + ); + + await vi.waitFor(() => expect(getTxStatus).toHaveBeenCalledTimes(1)); + await Effect.runPromise(Fiber.interrupt(fiber)); + await Promise.resolve(); + + expect(getTxStatus).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/widget/tests/providers/wallet/solana-connector-membership.test.ts b/packages/widget/tests/providers/wallet/solana-connector-membership.test.ts new file mode 100644 index 000000000..e68ebe6d4 --- /dev/null +++ b/packages/widget/tests/providers/wallet/solana-connector-membership.test.ts @@ -0,0 +1,155 @@ +import { + type Adapter, + type WalletName, + WalletReadyState, +} from "@solana/wallet-adapter-base"; +import { Connection } from "@solana/web3.js"; +import { Effect, Stream } from "effect"; +import type { Address } from "viem"; +import { describe, expect, it } from "vitest"; +import { createConfig, createConnector, http } from "wagmi"; +import { connect, disconnect, watchConnectors } from "wagmi/actions"; +import { solana } from "../../../src/domain/types/chains/misc"; +import type { SolanaRuntime } from "../../../src/services/wallet/platform/solana-platform"; +import { installSolanaConnectorMembership } from "../../../src/services/wallet/solana-connector-membership"; +import type { SolanaWalletDescriptor } from "../../../src/services/wallet/solana-runtime"; + +const account = "0x0000000000000000000000000000000000000501" as Address; + +const makeAdapter = (name: string) => ({ name: name as WalletName }) as Adapter; + +const descriptor = ( + adapter: Adapter, + source: SolanaWalletDescriptor["source"] +): SolanaWalletDescriptor => ({ + adapter, + readyState: WalletReadyState.Installed, + source, +}); + +const makeConnectorFactory = ( + wallet: SolanaWalletDescriptor, + operations: string[] +) => + createConnector((config) => ({ + $filteredChains: Stream.succeed([solana]), + id: wallet.adapter.name, + isSolanaConnector: true, + name: wallet.adapter.name, + rkDetails: { groupName: "Solana", installed: true }, + solanaAdapter: wallet.adapter, + solanaAdapterSource: wallet.source, + type: `solana-${wallet.source}`, + connect: async (parameters) => + ({ + accounts: parameters?.withCapabilities + ? [{ address: account, capabilities: {} }] + : [account], + chainId: solana.id, + }) as never, + disconnect: async () => { + operations.push(`disconnect:${wallet.source}`); + }, + getAccounts: async () => [account], + getChainId: async () => solana.id, + getProvider: async () => ({}), + isAuthorized: async () => false, + onAccountsChanged: () => undefined, + onChainChanged: () => undefined, + onDisconnect: () => config.emitter.emit("disconnect"), + sendTransaction: async () => "signature", + })); + +const makeHarness = ( + visible: SolanaWalletDescriptor, + discovered: SolanaWalletDescriptor +) => { + const operations: string[] = []; + const config = createConfig({ + chains: [solana], + connectors: [makeConnectorFactory(visible, operations)], + transports: { [solana.id]: http() }, + }); + const runtime = { + connection: new Connection("https://api.mainnet-beta.solana.com"), + current: Effect.succeed({ wallets: [discovered] }), + states: Stream.never, + } satisfies SolanaRuntime; + const core = { + current: Effect.succeed({ connection: {} as never, connectors: [] }), + states: Stream.never, + }; + return { + actions: { + disconnect: (input: Parameters[1]) => + Effect.tryPromise(() => disconnect(config, input)).pipe(Effect.orDie), + }, + config, + core, + createConnector: (wallet: SolanaWalletDescriptor) => + Effect.succeed(makeConnectorFactory(wallet, operations)), + operations, + runtime, + }; +}; + +describe("Solana connector membership", () => { + it("replaces an inactive same-name fallback during scoped setup", async () => { + const fallback = descriptor(makeAdapter("Phantom"), "fallback"); + const standard = descriptor(makeAdapter("Phantom"), "standard"); + const harness = makeHarness(fallback, standard); + + await Effect.runPromise( + Effect.scoped( + installSolanaConnectorMembership({ + actions: harness.actions, + config: harness.config, + core: harness.core, + createConnector: harness.createConnector, + runtime: harness.runtime, + }) + ) + ); + + expect(harness.config.connectors[0]).toMatchObject({ + solanaAdapter: standard.adapter, + solanaAdapterSource: "standard", + }); + }); + + it("disconnects an active Standard before publishing its fallback", async () => { + const standard = descriptor(makeAdapter("Phantom"), "standard"); + const fallback = descriptor(makeAdapter("Phantom"), "fallback"); + const harness = makeHarness(standard, fallback); + const visible = harness.config.connectors[0]!; + await connect(harness.config, { connector: visible }); + const unsubscribe = watchConnectors(harness.config, { + onChange: (connectors) => { + const connector = connectors[0]; + if (connector && "solanaAdapterSource" in connector) { + harness.operations.push( + `publish:${String(connector.solanaAdapterSource)}` + ); + } + }, + }); + + await Effect.runPromise( + Effect.scoped( + installSolanaConnectorMembership({ + actions: harness.actions, + config: harness.config, + core: harness.core, + createConnector: harness.createConnector, + runtime: harness.runtime, + }) + ) + ); + unsubscribe(); + + expect(harness.operations).toEqual([ + "disconnect:standard", + "publish:fallback", + ]); + }); +}); diff --git a/packages/widget/tests/providers/wallet/solana-runtime.test.ts b/packages/widget/tests/providers/wallet/solana-runtime.test.ts new file mode 100644 index 000000000..6b0125142 --- /dev/null +++ b/packages/widget/tests/providers/wallet/solana-runtime.test.ts @@ -0,0 +1,50 @@ +import { Effect, Layer, Stream } from "effect"; +import { describe, expect, it } from "vitest"; +import { + SolanaPlatform, + type SolanaRuntime, +} from "../../../src/services/wallet/platform/solana-platform"; + +describe("SolanaPlatform", () => { + it("constructs only a scoped connection when adapters are disabled", async () => { + const result = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const platform = yield* SolanaPlatform; + const runtime = yield* platform.makeRuntime({ + includeWalletAdapters: false, + }); + return { + endpoint: runtime.connection.rpcEndpoint, + snapshot: yield* runtime.current, + }; + }).pipe(Effect.provide(SolanaPlatform.layer)) + ) + ); + + expect(result.endpoint).toContain("solana.com"); + expect(result.snapshot).toEqual({ wallets: [] }); + }); + + it("is replaceable through an Effect Layer", async () => { + const expected = { wallets: [] } as const; + const fake = { + connection: { rpcEndpoint: "https://solana.test" }, + current: Effect.succeed(expected), + states: Stream.succeed(expected), + } as unknown as SolanaRuntime; + const layer = Layer.succeed( + SolanaPlatform, + SolanaPlatform.of({ makeRuntime: () => Effect.succeed(fake) }) + ); + + const runtime = await Effect.runPromise( + Effect.gen(function* () { + const platform = yield* SolanaPlatform; + return yield* platform.makeRuntime({ includeWalletAdapters: true }); + }).pipe(Effect.provide(layer), Effect.scoped) + ); + + expect(runtime).toBe(fake); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wagmi-actions.test.ts b/packages/widget/tests/providers/wallet/wagmi-actions.test.ts new file mode 100644 index 000000000..109b9de2f --- /dev/null +++ b/packages/widget/tests/providers/wallet/wagmi-actions.test.ts @@ -0,0 +1,202 @@ +import { Effect, Fiber } from "effect"; +import { type Hash, type Hex, zeroAddress } from "viem"; +import { describe, expect, it, vi } from "vitest"; +import { createConfig, http } from "wagmi"; +import { mainnet } from "wagmi/chains"; +import { + WagmiOperations, + WagmiOperationsError, + type WagmiOperationsService, +} from "../../../src/services/wallet/platform/wagmi-operations"; +import { makeWagmiActions } from "../../../src/services/wallet/wagmi-actions"; + +const connector = { + id: "test", + name: "Test", + type: "test", + uid: "test-uid", +} as Parameters[1]["connector"]; + +const makeConfig = () => + createConfig({ + chains: [mainnet], + connectors: [], + transports: { [mainnet.id]: http() }, + }); + +const makeOperations = (): WagmiOperationsService => ({ + connect: vi.fn(() => + Effect.succeed({ + accounts: [zeroAddress], + chainId: mainnet.id, + }) + ), + disconnect: vi.fn(() => Effect.void), + reconnect: vi.fn(() => Effect.succeed([])), + sendTransaction: vi.fn(() => Effect.succeed(`0x${"1".repeat(64)}` as Hash)), + signMessage: vi.fn(() => Effect.succeed("0xsigned" as Hex)), + switchChain: vi.fn(() => Effect.succeed(mainnet)), +}); + +const operationFailure = (cause: unknown) => + new WagmiOperationsError({ cause, operation: "connect" }); + +const makeCommands = ( + config: ReturnType, + operations: WagmiOperationsService +) => + Effect.runPromise( + makeWagmiActions({ config }).pipe( + Effect.provideService(WagmiOperations, WagmiOperations.of(operations)) + ) + ); + +describe("Wagmi actions", () => { + it("uses the exact controller config for every core action", async () => { + const config = makeConfig(); + const operations = makeOperations(); + const commands = await makeCommands(config, operations); + + await Effect.runPromise(commands.connect({ connector })); + await Effect.runPromise(commands.disconnect({ connector })); + await Effect.runPromise(commands.reconnect({ connectors: [connector] })); + await Effect.runPromise(commands.switchChain({ chainId: mainnet.id })); + await Effect.runPromise(commands.signMessage({ message: "hello" })); + await Effect.runPromise( + commands.sendEvmTransaction({ + data: "0x", + gasPrice: 1n, + to: zeroAddress, + type: "legacy", + }) + ); + + for (const operation of Object.values(operations)) { + expect(operation).toHaveBeenCalledWith(config, expect.anything()); + } + }); + + it("preserves action results and normalizes broadcast results", async () => { + const commands = await makeCommands(makeConfig(), makeOperations()); + + await expect( + Effect.runPromise(commands.connect({ connector })) + ).resolves.toEqual({ + accounts: [zeroAddress], + chainId: mainnet.id, + }); + await expect( + Effect.runPromise( + commands.sendEvmTransaction({ + maxFeePerGas: 2n, + maxPriorityFeePerGas: 1n, + to: zeroAddress, + type: "eip1559", + }) + ) + ).resolves.toEqual({ + broadcasted: true, + signedTx: `0x${"1".repeat(64)}`, + }); + }); + + it("uses the current Wagmi connection for EVM wallet actions", async () => { + const operations = makeOperations(); + const commands = await makeCommands(makeConfig(), operations); + + await Effect.runPromise( + commands.sendEvmTransaction({ + connector, + gasPrice: 1n, + to: zeroAddress, + type: "legacy", + }) + ); + await Effect.runPromise( + commands.signMessage({ connector, message: "hello" }) + ); + + expect(operations.sendTransaction).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ connector: undefined }) + ); + expect(operations.signMessage).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ connector: undefined }) + ); + }); + + it("maps platform failures by operation", async () => { + const cause = new Error("rejected"); + const operations = makeOperations(); + vi.mocked(operations.connect).mockReturnValue( + Effect.fail(operationFailure(cause)) + ); + vi.mocked(operations.switchChain).mockReturnValue( + Effect.fail(operationFailure(cause)) + ); + vi.mocked(operations.signMessage).mockReturnValue( + Effect.fail(operationFailure(cause)) + ); + vi.mocked(operations.sendTransaction).mockReturnValue( + Effect.fail(operationFailure(cause)) + ); + const commands = await makeCommands(makeConfig(), operations); + + const failures = await Promise.all([ + Effect.runPromise(Effect.flip(commands.connect({ connector }))), + Effect.runPromise( + Effect.flip(commands.switchChain({ chainId: mainnet.id })) + ), + Effect.runPromise( + Effect.flip(commands.signMessage({ message: "hello" })) + ), + Effect.runPromise( + Effect.flip( + commands.sendEvmTransaction({ + gasPrice: 1n, + to: zeroAddress, + type: "legacy", + }) + ) + ), + ]); + + expect(failures.map((failure) => failure._tag)).toEqual([ + "WalletConnectionError", + "WalletSwitchError", + "WalletSigningError", + "WalletBroadcastError", + ]); + expect(failures.every((failure) => failure.cause === cause)).toBe(true); + }); + + it("does not publish a late result from an interrupted wallet operation", async () => { + let resolve!: (value: Hex) => void; + let published = false; + const operations = makeOperations(); + vi.mocked(operations.signMessage).mockImplementation(() => + Effect.callback((resume) => { + resolve = (value) => resume(Effect.succeed(value)); + }) + ); + const commands = await makeCommands(makeConfig(), operations); + const fiber = Effect.runFork( + commands.signMessage({ message: "hello" }).pipe( + Effect.tap(() => + Effect.sync(() => { + published = true; + }) + ) + ) + ); + + await vi.waitFor(() => expect(operations.signMessage).toHaveBeenCalled()); + await Effect.runPromise(Fiber.interrupt(fiber)); + resolve("0xlate" as Hex); + await Promise.resolve(); + await Promise.resolve(); + + expect(published).toBe(false); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wallet-lifecycle.test.ts b/packages/widget/tests/providers/wallet/wallet-lifecycle.test.ts new file mode 100644 index 000000000..ec281769e --- /dev/null +++ b/packages/widget/tests/providers/wallet/wallet-lifecycle.test.ts @@ -0,0 +1,135 @@ +import { Effect, Layer, Schema } from "effect"; +import { mainnet } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { WalletAddress } from "../../../src/domain/schema/identifiers"; +import { TrackingService } from "../../../src/services/tracking/tracking-service"; +import type { NormalizedWalletState } from "../../../src/services/wallet/domain/state"; +import { disconnectedNormalizedWalletState } from "../../../src/services/wallet/domain/state"; +import { makeWalletLifecyclePolicy } from "../../../src/services/wallet/lifecycle"; + +const firstAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const secondAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" +); +const connector = { + id: "test", + name: "Test", + type: "test", + uid: "test-uid", +} as unknown as Connector; + +const connected = ( + address: typeof WalletAddress.Type = firstAddress +): NormalizedWalletState => ({ + additionalAddresses: null, + address, + chain: mainnet, + connector, + connectorChains: [mainnet], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum" as const, + status: "connected" as const, +}); + +const unsupported: NormalizedWalletState = { + ...disconnectedNormalizedWalletState, + address: firstAddress, + chain: mainnet, + connector, + connectorChains: [mainnet], + status: "unsupported" as const, +}; + +const makePolicy = (trackEvent: TrackingService["Service"]["trackEvent"]) => + makeWalletLifecyclePolicy.pipe( + Effect.provide( + Layer.succeed( + TrackingService, + TrackingService.of({ trackEvent } as TrackingService["Service"]) + ) + ) + ); + +describe("Wallet lifecycle policy", () => { + it("tracks each connected wallet identity once", async () => { + const trackEvent = vi.fn(() => Effect.void); + const disconnect = vi.fn(() => Effect.void); + + await Effect.runPromise( + Effect.gen(function* () { + const policy = yield* makePolicy(trackEvent as never); + yield* policy.transition({ + actions: { disconnect }, + state: connected(), + }); + yield* policy.transition({ + actions: { disconnect }, + state: connected(), + }); + yield* policy.transition({ + actions: { disconnect }, + state: connected(secondAddress), + }); + }) + ); + + expect(trackEvent).toHaveBeenCalledTimes(2); + expect(trackEvent).toHaveBeenLastCalledWith("connectedWallet", { + address: secondAddress, + network: "ethereum", + }); + expect(disconnect).not.toHaveBeenCalled(); + }); + + it("disconnects an unsupported identity once until state resets", async () => { + const disconnect = vi.fn(() => Effect.void); + + await Effect.runPromise( + Effect.gen(function* () { + const policy = yield* makePolicy(() => Effect.void); + yield* policy.transition({ + actions: { disconnect }, + state: unsupported, + }); + yield* policy.transition({ + actions: { disconnect }, + state: unsupported, + }); + yield* policy.transition({ + actions: { disconnect }, + state: disconnectedNormalizedWalletState, + }); + yield* policy.transition({ + actions: { disconnect }, + state: unsupported, + }); + }) + ); + + expect(disconnect).toHaveBeenCalledTimes(2); + expect(disconnect).toHaveBeenCalledWith({ connector }); + }); + + it("localizes tracking and disconnect failures", async () => { + await expect( + Effect.runPromise( + Effect.gen(function* () { + const policy = yield* makePolicy(() => Effect.die("tracking failed")); + yield* policy.transition({ + actions: { disconnect: () => Effect.die("disconnect failed") }, + state: connected(), + }); + yield* policy.transition({ + actions: { disconnect: () => Effect.die("disconnect failed") }, + state: unsupported, + }); + }) + ) + ).resolves.toBeUndefined(); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wallet-router.test.ts b/packages/widget/tests/providers/wallet/wallet-router.test.ts new file mode 100644 index 000000000..f7cd53dfe --- /dev/null +++ b/packages/widget/tests/providers/wallet/wallet-router.test.ts @@ -0,0 +1,126 @@ +import { Effect, Schema } from "effect"; +import { type Chain, type Hash, type Hex, zeroAddress } from "viem"; +import { describe, expect, it, vi } from "vitest"; +import type { Connector } from "wagmi"; +import { WalletAddress } from "../../../src/domain/schema/identifiers"; +import type { Network } from "../../../src/domain/schema/network-model"; +import { + disconnectedLedgerConnectorState, + disconnectedNormalizedWalletState, + type NormalizedWalletState, +} from "../../../src/services/wallet/domain/state"; +import { + routeWalletAccountSwitch, + routeWalletTransaction, + type WalletRoutingContext, +} from "../../../src/services/wallet/router"; +import type { WagmiActions } from "../../../src/services/wallet/wagmi-actions"; + +const transactionInput = { + ledgerHwAppId: null, + network: "ethereum" as Network, + tx: JSON.stringify({ + chainId: 1, + data: "0x", + from: zeroAddress, + gasLimit: "21000", + gasPrice: "1", + nonce: 1, + to: zeroAddress, + type: 0, + }), + txMeta: {} as never, +}; + +const makeConnector = (uid: string) => + ({ id: uid, uid }) as unknown as Connector; + +const walletAddress = Schema.decodeSync(WalletAddress)(zeroAddress); + +const connectedState = (connector: Connector): NormalizedWalletState => ({ + additionalAddresses: null, + address: walletAddress, + chain: { id: 1 } as Chain, + connector, + connectorChains: [], + isLedgerLive: false, + isLedgerLiveAccountPlaceholder: false, + ledgerAccounts: [], + network: "ethereum", + status: "connected", +}); + +const actions = () => + ({ + connect: vi.fn(() => Effect.die("unused")), + disconnect: vi.fn(() => Effect.die("unused")), + reconnect: vi.fn(() => Effect.die("unused")), + sendEvmTransaction: vi.fn(() => + Effect.succeed({ + broadcasted: true as const, + signedTx: "0xhash" as Hash, + }) + ), + signMessage: vi.fn(() => Effect.succeed("0xsignature" as Hex)), + switchChain: vi.fn(() => Effect.die("unused")), + }) satisfies WagmiActions; + +const routingContext = ( + state: NormalizedWalletState, + walletActions = actions() +): WalletRoutingContext => ({ + actions: walletActions, + cosmosChainWallet: null, + ledgerState: disconnectedLedgerConnectorState, + state, +}); + +describe("wallet router", () => { + it("fails commands with a typed capability error while disconnected", async () => { + const failure = await Effect.runPromise( + Effect.flip( + routeWalletTransaction( + routingContext(disconnectedNormalizedWalletState), + transactionInput + ) + ) + ); + + expect(failure).toMatchObject({ + _tag: "WalletCapabilityUnavailableError", + capability: "transaction", + connectorId: null, + }); + }); + + it("routes a generic EVM transaction through the bound Wagmi actions", async () => { + const connector = makeConnector("evm"); + const walletActions = actions(); + + await Effect.runPromise( + routeWalletTransaction( + routingContext(connectedState(connector), walletActions), + transactionInput + ) + ); + + expect(walletActions.sendEvmTransaction).toHaveBeenCalledWith( + expect.objectContaining({ connector }) + ); + }); + + it("rejects a stale Ledger account-switch command after wallet replacement", async () => { + const first = makeConnector("first"); + const second = makeConnector("second"); + const failure = await Effect.runPromise( + Effect.flip( + routeWalletAccountSwitch(routingContext(connectedState(second)), { + account: { id: "account" } as never, + connector: first, + }) + ) + ); + + expect(failure._tag).toBe("WalletCapabilityUnavailableError"); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wallet-service-acquisition.test.ts b/packages/widget/tests/providers/wallet/wallet-service-acquisition.test.ts new file mode 100644 index 000000000..2cccb379e --- /dev/null +++ b/packages/widget/tests/providers/wallet/wallet-service-acquisition.test.ts @@ -0,0 +1,364 @@ +import { + Deferred, + Effect, + Fiber, + Layer, + Option, + Stream, + SubscriptionRef, +} from "effect"; +import { TestClock } from "effect/testing"; +import { describe, expect, it } from "vitest"; +import type { Config } from "wagmi"; +import { getConnection, getConnectors } from "wagmi/actions"; +import { normalizeWidgetConfig } from "../../../src/app/config/settings"; +import { LegacyResourceSource } from "../../../src/services/api/legacy-resource-source"; +import { YieldResourceSource } from "../../../src/services/api/yield-resource-source"; +import { WidgetConfigService } from "../../../src/services/config/widget-config"; +import { WidgetPersistence } from "../../../src/services/persistence/widget-persistence"; +import { TrackingService } from "../../../src/services/tracking/tracking-service"; +import { WalletBootstrapError } from "../../../src/services/wallet/bootstrap"; +import { makeDefaultConfig } from "../../../src/services/wallet/default-wagmi-config"; +import { + SolanaPlatform, + type SolanaRuntime, +} from "../../../src/services/wallet/platform/solana-platform"; +import { + WagmiPlatform, + type WagmiPlatformService, +} from "../../../src/services/wallet/platform/wagmi-platform"; +import { WalletEnvironment } from "../../../src/services/wallet/platform/wallet-environment"; +import { WalletService } from "../../../src/services/wallet/wallet-service"; +import { makeWalletTestController } from "./wallet-test-controller"; + +const settings = normalizeWidgetConfig({ + apiKey: "api-key", + disableInjectedProviderDiscovery: true, + variant: "default", +}); + +const environmentLayer = Layer.succeed( + WalletEnvironment, + WalletEnvironment.of({ + href: Effect.succeed("https://widget.test/?network=ethereum"), + isLedgerDappBrowser: Effect.succeed(false), + isMobileWallet: Effect.succeed(false), + }) +); + +const solanaLayer = Layer.succeed( + SolanaPlatform, + SolanaPlatform.of({ + makeRuntime: () => + Effect.succeed({ + connection: {} as SolanaRuntime["connection"], + current: Effect.succeed({ wallets: [] }), + states: Stream.concat(Stream.succeed({ wallets: [] }), Stream.never), + }), + }) +); + +const apiLayer = Layer.mergeAll( + Layer.succeed(LegacyResourceSource, { + getEnabledNetworks: () => Effect.succeed(new Set(["ethereum"])), + } as never), + Layer.succeed(YieldResourceSource, { + getOpportunity: () => Effect.die("unused"), + } as never) +); + +const makeConfigLayer = ( + current = Effect.succeed(settings), + changes: Stream.Stream = Stream.never +) => + WidgetConfigService.layer({ + changes, + current, + initial: settings, + }); + +const makeWalletLayer = ( + wagmi: WagmiPlatformService, + configLayer = makeConfigLayer(), + walletApiLayer = apiLayer +) => { + const trackingLayer = TrackingService.layer.pipe(Layer.provide(configLayer)); + return WalletService.layer.pipe( + Layer.provide( + Layer.mergeAll( + walletApiLayer, + configLayer, + environmentLayer, + solanaLayer, + Layer.succeed(WagmiPlatform, WagmiPlatform.of(wagmi)), + trackingLayer, + WidgetPersistence.layer + ) + ) + ); +}; + +const makeObservation = (wagmiConfig: Config) => { + const core = { + connection: getConnection(wagmiConfig), + connectors: getConnectors(wagmiConfig), + }; + return { + current: Effect.succeed(core), + states: Stream.concat(Stream.succeed(core), Stream.never), + }; +}; + +describe("WalletService acquisition", () => { + it("blocks until bootstrap and initial connection finish", async () => { + const buildStarted = await Effect.runPromise(Deferred.make()); + const buildRelease = await Effect.runPromise(Deferred.make()); + const initializeStarted = await Effect.runPromise(Deferred.make()); + const initializeRelease = await Effect.runPromise(Deferred.make()); + const acquired = await Effect.runPromise( + Deferred.make() + ); + const releaseScope = await Effect.runPromise(Deferred.make()); + const wagmiConfig = makeDefaultConfig(); + const controller = makeWalletTestController({ + actions: {}, + queryParamsInitChainId: undefined, + wagmiConfig, + }); + const layer = makeWalletLayer({ + buildConfig: () => + Effect.gen(function* () { + yield* Deferred.succeed(buildStarted, undefined); + yield* Deferred.await(buildRelease); + return controller; + }), + initialize: () => + Effect.gen(function* () { + yield* Deferred.succeed(initializeStarted, undefined); + yield* Deferred.await(initializeRelease); + }), + observeCore: () => Effect.succeed(makeObservation(wagmiConfig)), + }); + + const fiber = Effect.runFork( + Effect.scoped( + Effect.gen(function* () { + const wallet = yield* WalletService; + yield* Deferred.succeed(acquired, wallet); + yield* Deferred.await(releaseScope); + }).pipe(Effect.provide(layer)) + ) + ); + + await Effect.runPromise(Deferred.await(buildStarted)); + expect( + Option.isNone(await Effect.runPromise(Deferred.poll(acquired))) + ).toBe(true); + await Effect.runPromise(Deferred.succeed(buildRelease, undefined)); + await Effect.runPromise(Deferred.await(initializeStarted)); + expect( + Option.isNone(await Effect.runPromise(Deferred.poll(acquired))) + ).toBe(true); + await Effect.runPromise(Deferred.succeed(initializeRelease, undefined)); + + const wallet = await Effect.runPromise(Deferred.await(acquired)); + const state = await Effect.runPromise(wallet.state); + expect(wallet.wagmiConfig).toBe(wagmiConfig); + expect(state.connection.status).toBe("disconnected"); + expect(state.ledger).toEqual({ + accounts: [], + currentAccountId: undefined, + disabledChains: [], + }); + + await Effect.runPromise(Deferred.succeed(releaseScope, undefined)); + await Effect.runPromise(Fiber.join(fiber)); + }); + + it("uses capability Layers and keeps one bootstrap config identity", async () => { + const wagmiConfig = makeDefaultConfig(); + const controller = makeWalletTestController({ + actions: {}, + queryParamsInitChainId: undefined, + wagmiConfig, + }); + const builds: unknown[] = []; + const initializations: unknown[] = []; + const layer = makeWalletLayer({ + buildConfig: (input) => + Effect.sync(() => { + builds.push(input); + return controller; + }), + initialize: (input) => + Effect.sync(() => { + initializations.push(input); + }), + observeCore: () => Effect.succeed(makeObservation(wagmiConfig)), + }); + + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const wallet = yield* WalletService; + expect(wallet.wagmiConfig).toBe(wagmiConfig); + expect((yield* wallet.state).connection.status).toBe("disconnected"); + }).pipe(Effect.provide(layer)) + ) + ); + + expect(builds).toHaveLength(1); + expect(builds[0]).toMatchObject({ + disableInjectedProviderDiscovery: true, + enabledNetworks: new Set(["ethereum"]), + queryParams: { network: "ethereum" }, + }); + expect(initializations).toEqual([expect.objectContaining({ wagmiConfig })]); + }); + + it("fails acquisition with the bootstrap stage and never exposes a service", async () => { + const cause = new Error("configuration failed"); + const layer = makeWalletLayer({ + buildConfig: () => Effect.fail({ cause } as never), + initialize: () => Effect.void, + observeCore: () => Effect.die("must not observe"), + }); + + const failure = await Effect.runPromise( + Effect.scoped(WalletService.pipe(Effect.provide(layer), Effect.flip)) + ); + + expect(failure).toBeInstanceOf(WalletBootstrapError); + expect(failure).toMatchObject({ stage: "wagmi-config" }); + expect((failure as WalletBootstrapError).cause).toEqual({ cause }); + }); + + it("fails acquisition after bounded enabled-network retries", async () => { + const cause = new Error("enabled networks unavailable"); + const failingApiLayer = Layer.mergeAll( + Layer.succeed(LegacyResourceSource, { + getEnabledNetworks: () => Effect.fail(cause), + } as never), + Layer.succeed(YieldResourceSource, { + getOpportunity: () => Effect.die("unused"), + } as never) + ); + const layer = makeWalletLayer( + { + buildConfig: () => Effect.die("must not build"), + initialize: () => Effect.void, + observeCore: () => Effect.die("must not observe"), + }, + makeConfigLayer(), + failingApiLayer + ); + + const failure = await Effect.runPromise( + Effect.gen(function* () { + const fiber = yield* Effect.scoped( + WalletService.pipe(Effect.provide(layer), Effect.flip) + ).pipe(Effect.forkChild({ startImmediately: true })); + yield* Effect.yieldNow; + yield* TestClock.adjust("4 seconds"); + return yield* Fiber.join(fiber); + }).pipe(Effect.provide(TestClock.layer())) + ); + + expect(failure).toMatchObject({ + _tag: "WalletBootstrapError", + cause, + stage: "enabled-networks", + }); + }); + + it("terminates state and commands when wallet topology changes", async () => { + const configRef = await Effect.runPromise(SubscriptionRef.make(settings)); + const configLayer = makeConfigLayer( + SubscriptionRef.get(configRef), + SubscriptionRef.changes(configRef) + ); + const wagmiConfig = makeDefaultConfig(); + const controller = makeWalletTestController({ + actions: {}, + queryParamsInitChainId: undefined, + wagmiConfig, + }); + const layer = makeWalletLayer( + { + buildConfig: () => Effect.succeed(controller), + initialize: () => Effect.void, + observeCore: () => Effect.succeed(makeObservation(wagmiConfig)), + }, + configLayer + ); + + const result = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const wallet = yield* WalletService; + const terminal = yield* wallet.states.pipe( + Stream.runDrain, + Effect.flip, + Effect.forkChild({ startImmediately: true }) + ); + yield* SubscriptionRef.set( + configRef, + normalizeWidgetConfig({ + apiKey: "api-key", + variant: "default", + wagmi: { forceWalletConnectOnly: true }, + }) + ); + const streamFailure = yield* Fiber.join(terminal); + const commandFailure = yield* wallet.disconnect().pipe(Effect.flip); + return { commandFailure, streamFailure }; + }).pipe(Effect.provide(layer)) + ) + ); + + expect(result.streamFailure).toMatchObject({ + _tag: "WalletRuntimeInvariantError", + reason: "wallet-topology-changed", + }); + expect(result.commandFailure).toBe(result.streamFailure); + }); + + it("constructs fresh scoped services after remount", async () => { + let builds = 0; + let initializations = 0; + const layer = makeWalletLayer({ + buildConfig: () => + Effect.sync(() => { + builds += 1; + return makeWalletTestController({ + actions: {}, + queryParamsInitChainId: undefined, + wagmiConfig: makeDefaultConfig(), + }); + }), + initialize: () => + Effect.sync(() => { + initializations += 1; + }), + observeCore: (controller) => + Effect.succeed(makeObservation(controller.wagmiConfig)), + }); + const mount = () => + Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const wallet = yield* WalletService; + return { config: wallet.wagmiConfig, wallet }; + }).pipe(Effect.provide(layer)) + ) + ); + + const first = await mount(); + const second = await mount(); + + expect(builds).toBe(2); + expect(initializations).toBe(2); + expect(second.wallet).not.toBe(first.wallet); + expect(second.config).not.toBe(first.config); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wallet-service-contract.test.ts b/packages/widget/tests/providers/wallet/wallet-service-contract.test.ts new file mode 100644 index 000000000..328cbee95 --- /dev/null +++ b/packages/widget/tests/providers/wallet/wallet-service-contract.test.ts @@ -0,0 +1,76 @@ +import type { Effect } from "effect"; +import { describe, expect, expectTypeOf, it } from "vitest"; +import { + WalletBroadcastError, + WalletCapabilityUnavailableError, + WalletConnectionError, + WalletDecodeError, + type WalletRuntimeInvariantError, + WalletSigningError, + WalletSwitchError, +} from "../../../src/services/wallet/domain/errors"; +import type { + WalletBroadcastResult, + WalletSignedPayloadResult, +} from "../../../src/services/wallet/domain/transactions"; +import type { WalletService } from "../../../src/services/wallet/wallet-service"; + +type WalletTransactionResult = + | WalletSignedPayloadResult + | WalletBroadcastResult; + +describe("wallet service contract", () => { + it("keeps signed payloads distinct from broadcast hashes", () => { + const signedPayload: WalletTransactionResult = { + broadcasted: false, + signedTx: "signed-payload", + }; + const broadcastHash: WalletTransactionResult = { + broadcasted: true, + signedTx: "transaction-hash", + }; + + if (signedPayload.broadcasted) { + expectTypeOf(signedPayload).toMatchTypeOf(); + } else { + expect(signedPayload.signedTx).toBe("signed-payload"); + } + expect(broadcastHash).toEqual({ + broadcasted: true, + signedTx: "transaction-hash", + }); + }); + + it("exposes operation-specific tagged failures", () => { + const cause = new Error("provider rejected"); + const failures = [ + new WalletConnectionError({ cause, operation: "connect" }), + new WalletSwitchError({ cause, operation: "chain", target: 1 }), + new WalletSigningError({ cause, operation: "transaction" }), + new WalletDecodeError({ cause }), + new WalletBroadcastError({ cause, customMessage: "Try again" }), + new WalletCapabilityUnavailableError({ + capability: "account", + connectorId: "injected", + }), + ]; + + expect(failures.map((failure) => failure._tag)).toEqual([ + "WalletConnectionError", + "WalletSwitchError", + "WalletSigningError", + "WalletDecodeError", + "WalletBroadcastError", + "WalletCapabilityUnavailableError", + ]); + expect(failures.every((failure) => failure instanceof Error)).toBe(true); + }); + + it("defines Effect commands without a React dependency", () => { + expectTypeOf< + WalletService["Service"]["disconnect"] + >().returns.toEqualTypeOf< + Effect.Effect + >(); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wallet-state.test.ts b/packages/widget/tests/providers/wallet/wallet-state.test.ts new file mode 100644 index 000000000..447299c3d --- /dev/null +++ b/packages/widget/tests/providers/wallet/wallet-state.test.ts @@ -0,0 +1,269 @@ +import { + Deferred, + Effect, + Fiber, + Layer, + Option, + Stream, + SubscriptionRef, +} from "effect"; +import { mainnet, optimism } from "viem/chains"; +import { describe, expect, it } from "vitest"; +import type { Connector } from "wagmi"; +import { normalizeWidgetConfig } from "../../../src/app/config/settings"; +import { LegacyResourceSource } from "../../../src/services/api/legacy-resource-source"; +import { YieldResourceSource } from "../../../src/services/api/yield-resource-source"; +import { WidgetConfigService } from "../../../src/services/config/widget-config"; +import { WidgetPersistence } from "../../../src/services/persistence/widget-persistence"; +import { TrackingService } from "../../../src/services/tracking/tracking-service"; +import { makeDefaultConfig } from "../../../src/services/wallet/default-wagmi-config"; +import type { WalletCoreState } from "../../../src/services/wallet/domain/state"; +import { + SolanaPlatform, + type SolanaRuntime, +} from "../../../src/services/wallet/platform/solana-platform"; +import { WagmiPlatform } from "../../../src/services/wallet/platform/wagmi-platform"; +import { WalletEnvironment } from "../../../src/services/wallet/platform/wallet-environment"; +import type { WalletController } from "../../../src/services/wallet/wagmi-config"; +import { WalletService } from "../../../src/services/wallet/wallet-service"; +import { makeWalletStateRuntime } from "../../../src/services/wallet/wallet-state"; +import { makeWalletTestController } from "./wallet-test-controller"; + +const address = "0x0000000000000000000000000000000000000001"; + +const disconnectedConnection = { + address: undefined, + addresses: undefined, + chain: undefined, + chainId: undefined, + connector: undefined, + isConnected: false, + isConnecting: false, + isDisconnected: true, + isReconnecting: false, + status: "disconnected", +} as const satisfies WalletCoreState["connection"]; + +const connectedConnection = ( + connector: Connector +): WalletCoreState["connection"] => ({ + address, + addresses: [address], + chain: mainnet, + chainId: mainnet.id, + connector, + isConnected: true, + isConnecting: false, + isDisconnected: false, + isReconnecting: false, + status: "connected", +}); + +const makeController = ( + wagmiConfig: ReturnType, + actions: Parameters[0]["actions"] = {} +) => + makeWalletTestController({ + actions, + evmConfig: { + evmChains: [mainnet], + evmChainsMap: { + ethereum: { skChainName: "ethereum", wagmiChain: mainnet }, + }, + }, + queryParamsInitChainId: undefined, + wagmiConfig, + }); + +describe("WalletService authoritative Wallet State", () => { + it("keeps an in-flight command on its captured routing context", async () => { + const firstStarted = await Effect.runPromise(Deferred.make()); + const firstRelease = await Effect.runPromise(Deferred.make()); + const firstConnector = { + id: "first", + name: "First", + type: "injected", + uid: "first-uid", + } as unknown as Connector; + const secondConnector = { + id: "second", + name: "Second", + type: "injected", + uid: "second-uid", + } as unknown as Connector; + const routed: string[] = []; + const wagmiConfig = makeDefaultConfig(); + const controller = makeController(wagmiConfig, { + signMessage: ({ connector }: { readonly connector: Connector }) => + Effect.gen(function* () { + routed.push(connector.uid); + if (connector.uid === firstConnector.uid) { + yield* Deferred.succeed(firstStarted, undefined); + yield* Deferred.await(firstRelease); + } + return connector.uid; + }), + }); + const core = await Effect.runPromise( + SubscriptionRef.make({ + connection: connectedConnection(firstConnector), + connectors: [firstConnector, secondConnector], + }) + ); + const settings = normalizeWidgetConfig({ + apiKey: "api-key", + disableInjectedProviderDiscovery: true, + variant: "default", + }); + const configLayer = WidgetConfigService.layer({ + changes: Stream.never, + current: Effect.succeed(settings), + initial: settings, + }); + const layer = WalletService.layer.pipe( + Layer.provide( + Layer.mergeAll( + configLayer, + Layer.succeed(LegacyResourceSource, { + getEnabledNetworks: () => Effect.succeed(new Set(["ethereum"])), + } as never), + Layer.succeed(YieldResourceSource, { + getOpportunity: () => Effect.die("unused"), + } as never), + Layer.succeed( + WalletEnvironment, + WalletEnvironment.of({ + href: Effect.succeed("https://widget.test/"), + isLedgerDappBrowser: Effect.succeed(false), + isMobileWallet: Effect.succeed(false), + }) + ), + Layer.succeed( + SolanaPlatform, + SolanaPlatform.of({ + makeRuntime: () => + Effect.succeed({ + connection: {} as SolanaRuntime["connection"], + current: Effect.succeed({ wallets: [] }), + states: Stream.never, + }), + }) + ), + Layer.succeed( + WagmiPlatform, + WagmiPlatform.of({ + buildConfig: () => Effect.succeed(controller), + initialize: () => Effect.void, + observeCore: () => + Effect.succeed({ + current: SubscriptionRef.get(core), + states: SubscriptionRef.changes(core), + }), + }) + ), + TrackingService.layer.pipe(Layer.provide(configLayer)), + WidgetPersistence.layer + ) + ) + ); + + const result = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const wallet = yield* WalletService; + const first = yield* wallet + .signMessage({ message: "first" }) + .pipe(Effect.forkChild({ startImmediately: true })); + yield* Deferred.await(firstStarted); + const changed = yield* wallet.states.pipe( + Stream.filter( + (state) => + state.connection.status === "connected" && + state.connection.connector.uid === secondConnector.uid + ), + Stream.runHead, + Effect.map(Option.getOrThrow), + Effect.forkChild({ startImmediately: true }) + ); + yield* SubscriptionRef.set(core, { + connection: connectedConnection(secondConnector), + connectors: [firstConnector, secondConnector], + }); + yield* Fiber.join(changed); + const second = yield* wallet.signMessage({ message: "second" }); + yield* Deferred.succeed(firstRelease, undefined); + return { first: yield* Fiber.join(first), second }; + }).pipe(Effect.provide(layer)) + ) + ); + + expect(result).toEqual({ first: "first-uid", second: "second-uid" }); + expect(routed).toEqual(["first-uid", "second-uid"]); + }); + + it("publishes a connected state only after enrichment completes", async () => { + const enrichmentStarted = await Effect.runPromise(Deferred.make()); + const enrichmentRelease = await Effect.runPromise( + Deferred.make>() + ); + const connector = { + $filteredChains: Stream.fromEffect( + Effect.gen(function* () { + yield* Deferred.succeed(enrichmentStarted, undefined); + return yield* Deferred.await(enrichmentRelease); + }) + ), + id: "controlled", + name: "Controlled", + type: "controlled", + uid: "controlled-uid", + } as unknown as Connector; + const controller = makeController(makeDefaultConfig()); + const core = await Effect.runPromise( + SubscriptionRef.make({ + connection: disconnectedConnection, + connectors: [connector], + }) + ); + + const result = await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const state = yield* makeWalletStateRuntime({ + controller: controller as WalletController, + core: { + current: SubscriptionRef.get(core), + states: SubscriptionRef.changes(core), + }, + readStoredPublicKeys: Effect.succeed({}), + }); + const connected = yield* state.contexts.pipe( + Stream.filter( + (context) => context.state.connection.status === "connected" + ), + Stream.runHead, + Effect.map(Option.getOrThrow), + Effect.forkChild({ startImmediately: true }) + ); + yield* SubscriptionRef.set(core, { + connection: connectedConnection(connector), + connectors: [connector], + }); + yield* Deferred.await(enrichmentStarted); + const whileEnriching = yield* state.context; + yield* Deferred.succeed(enrichmentRelease, [optimism]); + return { + connected: yield* Fiber.join(connected), + whileEnriching, + }; + }) + ) + ); + + expect(result.whileEnriching.state.connection.status).toBe("disconnected"); + expect(result.connected.state.connection).toMatchObject({ + connectorChains: [optimism], + status: "connected", + }); + }); +}); diff --git a/packages/widget/tests/providers/wallet/wallet-test-controller.ts b/packages/widget/tests/providers/wallet/wallet-test-controller.ts new file mode 100644 index 000000000..06cdaf4ac --- /dev/null +++ b/packages/widget/tests/providers/wallet/wallet-test-controller.ts @@ -0,0 +1,13 @@ +import type { WalletController } from "../../../src/services/wallet/wagmi-config"; + +export const makeWalletTestController = ( + controller: Record +): WalletController => + ({ + cosmosConfig: { cosmosChainsMap: {} }, + evmConfig: { evmChains: [], evmChainsMap: {} }, + isLedgerLive: false, + miscConfig: { miscChainsMap: {} }, + substrateConfig: { substrateChainsMap: {} }, + ...controller, + }) as unknown as WalletController; diff --git a/packages/widget/tests/providers/widget-runtime-services.test.ts b/packages/widget/tests/providers/widget-runtime-services.test.ts new file mode 100644 index 000000000..85bda7abb --- /dev/null +++ b/packages/widget/tests/providers/widget-runtime-services.test.ts @@ -0,0 +1,189 @@ +import { Cause, Deferred, Effect, Fiber, Option, Stream } from "effect"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { BorrowResourceSource } from "../../src/services/api/borrow-resource-source"; +import { + type WidgetConfig, + WidgetConfigService, +} from "../../src/services/config/widget-config"; +import { TrackingService } from "../../src/services/tracking/tracking-service"; + +const firstTrackingProbeAtom = appRuntime.atom( + TrackingService.use((tracking) => Effect.succeed(tracking)) +); +const secondTrackingProbeAtom = appRuntime.atom( + TrackingService.use((tracking) => Effect.succeed(tracking)) +); +const borrowIntegrationsProbeAtom = appRuntime.atom( + BorrowResourceSource.use((borrow) => borrow.getIntegrations()) +); +const widgetConfigProbeAtom = appRuntime.atom( + WidgetConfigService.use((config) => Effect.succeed(config)) +); + +const makeConfig = (trackEvent: (event: string, properties?: object) => void) => + normalizeWidgetConfig({ + apiKey: "test-api-key", + tracking: { trackEvent }, + variant: "default", + }); + +const runtimeInitialValues = (config: WidgetConfig) => + [[widgetConfigAtom, config]] as const; + +describe("widget runtime service graph", () => { + it("exposes the current widget config and registry-scoped changes", async () => { + const firstTrack = vi.fn(); + const replacementTrack = vi.fn(); + const initialConfig = makeConfig(firstTrack); + const replacementConfig = makeConfig(replacementTrack); + const registry = AtomRegistry.make({ + initialValues: runtimeInitialValues(initialConfig), + }); + + try { + const config = AsyncResult.getOrThrow( + registry.get(widgetConfigProbeAtom) + ); + expect(config.initial).toBe(initialConfig); + const ready = await Effect.runPromise(Deferred.make()); + const changesFiber = Effect.runFork( + config.changes.pipe( + Stream.tap(() => Deferred.succeed(ready, undefined)), + Stream.take(2), + Stream.runCollect + ) + ); + + await Effect.runPromise(Deferred.await(ready)); + expect(await Effect.runPromise(config.current)).toBe(initialConfig); + + registry.set(widgetConfigAtom, replacementConfig); + + const changes = Array.from( + await Effect.runPromise(Fiber.join(changesFiber)) + ); + expect(changes).toEqual([initialConfig, replacementConfig]); + expect(await Effect.runPromise(config.current)).toBe(replacementConfig); + } finally { + registry.dispose(); + } + }); + + it("shares static service layers within a registry and isolates registries", async () => { + const firstTrack = vi.fn(); + const secondTrack = vi.fn(); + const firstRegistry = AtomRegistry.make({ + initialValues: runtimeInitialValues(makeConfig(firstTrack)), + }); + const secondRegistry = AtomRegistry.make({ + initialValues: runtimeInitialValues(makeConfig(secondTrack)), + }); + + try { + const firstService = AsyncResult.getOrThrow( + firstRegistry.get(firstTrackingProbeAtom) + ); + const sameRegistryService = AsyncResult.getOrThrow( + firstRegistry.get(secondTrackingProbeAtom) + ); + const secondService = AsyncResult.getOrThrow( + secondRegistry.get(firstTrackingProbeAtom) + ); + + expect(sameRegistryService).toBe(firstService); + expect(secondService).not.toBe(firstService); + + await Effect.runPromise( + Effect.all([ + firstService.trackEvent("txSigned", { registry: "first" }), + secondService.trackEvent("txSigned", { registry: "second" }), + ]) + ); + + expect(firstTrack).toHaveBeenCalledOnce(); + expect(firstTrack).toHaveBeenCalledWith("Transaction signed", { + registry: "first", + }); + expect(secondTrack).toHaveBeenCalledOnce(); + expect(secondTrack).toHaveBeenCalledWith("Transaction signed", { + registry: "second", + }); + + const replacementTrack = vi.fn(); + firstRegistry.set(widgetConfigAtom, makeConfig(replacementTrack)); + await Effect.runPromise( + firstService.trackEvent("txSigned", { registry: "first-updated" }) + ); + + expect(firstTrack).toHaveBeenCalledOnce(); + expect(replacementTrack).toHaveBeenCalledWith("Transaction signed", { + registry: "first-updated", + }); + expect(secondTrack).toHaveBeenCalledOnce(); + } finally { + firstRegistry.dispose(); + secondRegistry.dispose(); + } + }); + + it("creates fresh lifecycle-sensitive services after a registry remount", () => { + const config = makeConfig(vi.fn()); + const firstRegistry = AtomRegistry.make({ + initialValues: runtimeInitialValues(config), + }); + const firstService = AsyncResult.getOrThrow( + firstRegistry.get(firstTrackingProbeAtom) + ); + + firstRegistry.dispose(); + + const remountedRegistry = AtomRegistry.make({ + initialValues: runtimeInitialValues(config), + }); + + try { + expect( + AsyncResult.getOrThrow(remountedRegistry.get(firstTrackingProbeAtom)) + ).not.toBe(firstService); + } finally { + remountedRegistry.dispose(); + } + }); + + it("keeps the runtime available when a Borrow operation is unavailable", () => { + const config = normalizeWidgetConfig({ + apiKey: "test-api-key", + borrowApiUrl: "", + tracking: { trackEvent: vi.fn() }, + variant: "default", + }); + const registry = AtomRegistry.make({ + initialValues: runtimeInitialValues(config), + }); + + try { + expect(AsyncResult.isSuccess(registry.get(firstTrackingProbeAtom))).toBe( + true + ); + + const result = registry.get(borrowIntegrationsProbeAtom); + expect(AsyncResult.isFailure(result)).toBe(true); + + if (AsyncResult.isFailure(result)) { + const error = Cause.findErrorOption(result.cause); + expect(Option.isSome(error) && error.value._tag).toBe( + "MissingBorrowApiConfig" + ); + } + } finally { + registry.dispose(); + } + }); +}); diff --git a/packages/widget/tests/resources/activity-history.test.ts b/packages/widget/tests/resources/activity-history.test.ts new file mode 100644 index 000000000..02e63ccaa --- /dev/null +++ b/packages/widget/tests/resources/activity-history.test.ts @@ -0,0 +1,229 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + ActivityHistoryError, + ActivityHistoryKey, + activityCountResourceAtom, + activityHistoryPullAtom, +} from "../../src/resources/activity-history/activity-history"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { getPullResultItems } from "../../src/shared/effect/pagination"; +import { yieldApiActionFixture, yieldApiYieldFixture } from "../fixtures"; + +const scope = new WalletScopeKey({ + address: Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" + ), + network: "ethereum", +}); +const yieldId = yieldApiYieldFixture().id; +const makeKey = () => + new ActivityHistoryKey({ + scope, + statuses: ["SUCCESS", "FAILED", "SUCCESS"], + }); +const makeRegistry = (listActivity: ReturnType) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ listActivity } as never) + ) + ), + ], + }); + +describe("Activity History resource", () => { + it("shares normalized requests and loads one semantic batch per Pull", async () => { + const listActivity = vi.fn((request: { readonly offset: number }) => + Effect.succeed({ + items: [ + yieldApiActionFixture({ + id: request.offset === 0 ? "action-a" : "action-b", + yieldId, + }), + ], + limit: 1, + offset: request.offset, + total: 2, + }) + ); + const registry = makeRegistry(listActivity); + const firstPull = activityHistoryPullAtom(makeKey()); + const equivalentPull = activityHistoryPullAtom(makeKey()); + const unmount = registry.mount(firstPull); + + await vi.waitFor(() => + expect(getPullResultItems(registry.get(firstPull))).toMatchObject([ + { actions: [{ id: "action-a" }], total: 2 }, + ]) + ); + expect(AsyncResult.getOrThrow(registry.get(firstPull)).done).toBe(false); + expect(firstPull).toBe(equivalentPull); + expect(listActivity).toHaveBeenCalledOnce(); + + registry.set(firstPull, undefined); + + await vi.waitFor(() => + expect(getPullResultItems(registry.get(firstPull))).toMatchObject([ + { actions: [{ id: "action-a" }], total: 2 }, + { actions: [{ id: "action-b" }], total: 2 }, + ]) + ); + expect(AsyncResult.getOrThrow(registry.get(firstPull)).done).toBe(true); + expect(listActivity).toHaveBeenCalledTimes(2); + expect(listActivity.mock.calls.map(([request]) => request.offset)).toEqual([ + 0, 1, + ]); + + unmount(); + registry.dispose(); + }); + + it("shares history when only unused additional addresses differ", () => { + const listActivity = vi.fn((request: { readonly offset: number }) => + Effect.succeed({ + items: [], + limit: 50, + offset: request.offset, + total: 0, + }) + ); + const registry = makeRegistry(listActivity); + const scopeWithAdditionalAddress = new WalletScopeKey({ + additionalAddresses: { binanceBeaconAddress: "bnb-address" }, + address: scope.address, + network: scope.network, + }); + const first = makeKey(); + const second = new ActivityHistoryKey({ + scope: scopeWithAdditionalAddress, + statuses: ["FAILED", "SUCCESS"], + }); + + expect(second).toEqual(first); + expect( + getPullResultItems(registry.get(activityHistoryPullAtom(first))) + ).toMatchObject([{ actions: [], total: 0 }]); + expect( + getPullResultItems(registry.get(activityHistoryPullAtom(second))) + ).toMatchObject([{ actions: [], total: 0 }]); + expect(listActivity).toHaveBeenCalledOnce(); + }); + + it("loads a bounded count without collecting history", () => { + const listActivity = vi.fn((request: { readonly limit: number }) => + Effect.succeed({ + items: [yieldApiActionFixture({ id: "ignored", yieldId })], + limit: request.limit, + offset: 0, + total: 123, + }) + ); + const registry = makeRegistry(listActivity); + + expect( + AsyncResult.getOrThrow(registry.get(activityCountResourceAtom(makeKey()))) + ).toBe(123); + expect(listActivity).toHaveBeenCalledOnce(); + expect(listActivity.mock.calls[0]?.[0]).toMatchObject({ + limit: 1, + offset: 0, + }); + }); + + it("retains the first page after a later failure and refreshes from page one", async () => { + let failSecondPage = true; + const firstAction = yieldApiActionFixture({ + id: "action-a", + yieldId, + }); + const listActivity = vi.fn((request: { readonly offset: number }) => + request.offset > 0 && failSecondPage + ? Effect.fail( + new ApiRequestError({ + cause: new Error("offline"), + operation: "activity-history", + }) + ) + : Effect.succeed({ + items: [firstAction], + limit: 1, + offset: request.offset, + total: 2, + }) + ); + const registry = makeRegistry(listActivity); + const pull = activityHistoryPullAtom(makeKey()); + const unmount = registry.mount(pull); + + await vi.waitFor(() => + expect(getPullResultItems(registry.get(pull))).toMatchObject([ + { actions: [{ id: "action-a" }], total: 2 }, + ]) + ); + registry.set(pull, undefined); + await vi.waitFor(() => + expect(AsyncResult.isFailure(registry.get(pull))).toBe(true) + ); + expect( + registry.get(pull).pipe(AsyncResult.value, Option.getOrThrow).items + ).toMatchObject([{ actions: [{ id: "action-a" }], total: 2 }]); + + failSecondPage = false; + registry.refresh(pull); + + await vi.waitFor(() => + expect(AsyncResult.isSuccess(registry.get(pull))).toBe(true) + ); + expect(listActivity.mock.calls.map(([request]) => request.offset)).toEqual([ + 0, 1, 0, + ]); + + unmount(); + registry.dispose(); + }); + + it("publishes typed failures and retries the exact history", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "activity-history", + }); + const listActivity = vi.fn((request: { readonly offset: number }) => + offline + ? Effect.fail(requestError) + : Effect.succeed({ + items: [], + limit: 50, + offset: request.offset, + total: 0, + }) + ); + const registry = makeRegistry(listActivity); + const key = makeKey(); + const resource = activityHistoryPullAtom(key); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(ActivityHistoryError); + + const attemptsBeforeRetry = listActivity.mock.calls.length; + offline = false; + registry.refresh(resource); + expect(getPullResultItems(registry.get(resource))).toMatchObject([ + { actions: [], total: 0 }, + ]); + expect(listActivity).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); +}); diff --git a/packages/widget/tests/resources/earn-token-discovery.test.ts b/packages/widget/tests/resources/earn-token-discovery.test.ts new file mode 100644 index 000000000..2d264f9bb --- /dev/null +++ b/packages/widget/tests/resources/earn-token-discovery.test.ts @@ -0,0 +1,284 @@ +import { Cause, Effect, Layer, Option } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { legacyTokenOptionsResourceAtom } from "../../src/resources/legacy-token-options/legacy-token-options"; +import { + YieldTokensError, + YieldTokensKey, + yieldTokensPullAtom, +} from "../../src/resources/yield-token-directory/yield-token-directory"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { + YieldResourceSource, + type YieldTokenDirectoryRequest, +} from "../../src/services/api/yield-resource-source"; +import { yieldApiYieldFixture } from "../fixtures"; + +const yieldModel = yieldApiYieldFixture(); +const tokenOption = { + availableYields: [yieldModel.id], + token: yieldModel.token, +}; + +const makeRegistry = ({ + legacy, + yieldSource, +}: { + readonly legacy?: LegacyResourceSource["Service"]; + readonly yieldSource?: YieldResourceSource["Service"]; +}) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + (legacy + ? Layer.succeed(LegacyResourceSource, legacy) + : Layer.succeed(YieldResourceSource, yieldSource!)) as never + ), + ], + }); + +describe("Earn token discovery resources", () => { + it("shares normalized Yield filters and loads one page per Pull", async () => { + const items = Array.from({ length: 101 }, () => tokenOption); + const listYieldTokens = vi.fn((request: YieldTokenDirectoryRequest) => + Effect.succeed({ + items: items.slice(request.offset, request.offset + 60), + limit: 60, + offset: request.offset, + total: items.length, + }) + ); + const registry = makeRegistry({ + yieldSource: YieldResourceSource.of({ listYieldTokens } as never), + }); + const first = new YieldTokensKey({ + networks: ["ethereum", "ethereum"], + yieldTypes: ["staking", "staking"], + }); + const equivalent = new YieldTokensKey({ + networks: ["ethereum"], + yieldTypes: ["staking"], + }); + const firstPull = yieldTokensPullAtom(first); + const equivalentPull = yieldTokensPullAtom(equivalent); + const unmount = registry.mount(firstPull); + + await vi.waitFor(() => + expect( + AsyncResult.getOrThrow(registry.get(firstPull)).items.flatMap( + (page) => page.items + ) + ).toHaveLength(60) + ); + expect(AsyncResult.getOrThrow(registry.get(firstPull)).done).toBe(false); + expect(firstPull).toBe(equivalentPull); + expect(listYieldTokens).toHaveBeenCalledOnce(); + + registry.set(firstPull, undefined); + + await vi.waitFor(() => + expect( + AsyncResult.getOrThrow(registry.get(firstPull)).items.flatMap( + (page) => page.items + ) + ).toHaveLength(101) + ); + expect(AsyncResult.getOrThrow(registry.get(firstPull)).done).toBe(true); + expect(listYieldTokens).toHaveBeenCalledTimes(2); + expect( + listYieldTokens.mock.calls.map(([request]) => request.offset) + ).toEqual([0, 60]); + + unmount(); + registry.dispose(); + }); + + it("does not skip an empty intermediate page", async () => { + const listYieldTokens = vi.fn((request: YieldTokenDirectoryRequest) => + Effect.succeed({ + items: request.offset === 0 ? [] : [tokenOption], + limit: 1, + offset: request.offset, + total: 2, + }) + ); + const registry = makeRegistry({ + yieldSource: YieldResourceSource.of({ listYieldTokens } as never), + }); + const pull = yieldTokensPullAtom( + new YieldTokensKey({ networks: ["ethereum"] }) + ); + const unmount = registry.mount(pull); + + await vi.waitFor(() => expect(listYieldTokens).toHaveBeenCalledOnce()); + expect( + AsyncResult.getOrThrow(registry.get(pull)).items.flatMap( + (page) => page.items + ) + ).toEqual([]); + + registry.set(pull, undefined); + + await vi.waitFor(() => + expect( + AsyncResult.getOrThrow(registry.get(pull)).items.flatMap( + (page) => page.items + ) + ).toEqual([tokenOption]) + ); + expect(listYieldTokens).toHaveBeenCalledTimes(2); + + unmount(); + registry.dispose(); + }); + + it("retains the first page after a later failure and refreshes from page one", async () => { + let failSecondPage = true; + const listYieldTokens = vi.fn((request: YieldTokenDirectoryRequest) => + request.offset > 0 && failSecondPage + ? Effect.fail( + new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-token-directory", + }) + ) + : Effect.succeed({ + items: [tokenOption], + limit: 1, + offset: request.offset, + total: 2, + }) + ); + const registry = makeRegistry({ + yieldSource: YieldResourceSource.of({ listYieldTokens } as never), + }); + const pull = yieldTokensPullAtom( + new YieldTokensKey({ networks: ["ethereum"] }) + ); + const unmount = registry.mount(pull); + + await vi.waitFor(() => + expect( + AsyncResult.getOrThrow(registry.get(pull)).items.flatMap( + (page) => page.items + ) + ).toEqual([tokenOption]) + ); + registry.set(pull, undefined); + await vi.waitFor(() => + expect(AsyncResult.isFailure(registry.get(pull))).toBe(true) + ); + expect( + registry + .get(pull) + .pipe(AsyncResult.value, Option.getOrThrow) + .items.flatMap((page) => page.items) + ).toEqual([tokenOption]); + + failSecondPage = false; + registry.refresh(pull); + + await vi.waitFor(() => + expect(AsyncResult.isSuccess(registry.get(pull))).toBe(true) + ); + expect( + listYieldTokens.mock.calls.map(([request]) => request.offset) + ).toEqual([0, 1, 0]); + + unmount(); + registry.dispose(); + }); + + it("treats explicit empty filters as an empty fact without I/O", () => { + const listYieldTokens = vi.fn(() => Effect.die("unused")); + const registry = makeRegistry({ + yieldSource: YieldResourceSource.of({ listYieldTokens } as never), + }); + + const result = registry.get( + yieldTokensPullAtom(new YieldTokensKey({ networks: [] })) + ); + + expect(AsyncResult.getOrThrow(result)).toMatchObject({ + done: true, + items: [{ items: [] }], + }); + expect(listYieldTokens).not.toHaveBeenCalled(); + }); + + it("shares Legacy options by network and separates distinct networks", () => { + const getTokenOptions = vi.fn(() => Effect.succeed([tokenOption])); + const registry = makeRegistry({ + legacy: LegacyResourceSource.of({ getTokenOptions } as never), + }); + + registry.get(legacyTokenOptionsResourceAtom("ethereum")); + registry.get(legacyTokenOptionsResourceAtom("ethereum")); + registry.get(legacyTokenOptionsResourceAtom("base")); + + expect(getTokenOptions).toHaveBeenCalledTimes(2); + expect(getTokenOptions.mock.calls).toEqual([["ethereum"], ["base"]]); + }); + + it("publishes typed Yield failures and retries the same filters", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-token-directory", + }); + const listYieldTokens = vi.fn((request: YieldTokenDirectoryRequest) => + offline + ? Effect.fail(requestError) + : Effect.succeed({ + items: [], + limit: request.limit, + offset: request.offset, + total: 0, + }) + ); + const registry = makeRegistry({ + yieldSource: YieldResourceSource.of({ listYieldTokens } as never), + }); + const key = new YieldTokensKey({ networks: ["ethereum"] }); + const resource = yieldTokensPullAtom(key); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(YieldTokensError); + + const attemptsBeforeRetry = listYieldTokens.mock.calls.length; + offline = false; + registry.refresh(resource); + + expect( + AsyncResult.getOrThrow(registry.get(resource)).items.flatMap( + (page) => page.items + ) + ).toEqual([]); + expect(listYieldTokens).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); + + it("retries missing Legacy data through the exact network resource", () => { + let available = false; + const getTokenOptions = vi.fn(() => + Effect.succeed(available ? [tokenOption] : []) + ); + const registry = makeRegistry({ + legacy: LegacyResourceSource.of({ getTokenOptions } as never), + }); + const resource = legacyTokenOptionsResourceAtom("ethereum"); + + expect(AsyncResult.getOrThrow(registry.get(resource))).toEqual([]); + available = true; + registry.refresh(resource); + expect(AsyncResult.getOrThrow(registry.get(resource))).toEqual([ + tokenOption, + ]); + }); +}); diff --git a/packages/widget/tests/resources/flow-balance-facts.test.ts b/packages/widget/tests/resources/flow-balance-facts.test.ts new file mode 100644 index 000000000..69f617722 --- /dev/null +++ b/packages/widget/tests/resources/flow-balance-facts.test.ts @@ -0,0 +1,142 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + GasTokenBalancesKey, + gasTokenBalancesResourceAtom, +} from "../../src/resources/gas-token-balances/gas-token-balances"; +import { + SingleYieldBalancesError, + SingleYieldBalancesKey, + singleYieldBalancesResourceAtom, +} from "../../src/resources/single-yield-balances/single-yield-balances"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const scope = new WalletScopeKey({ address, network: "ethereum" }); +const yieldId = yieldApiYieldFixture().id; +const singleKey = () => + new SingleYieldBalancesKey({ address: scope.address, yieldId }); +const gasKey = () => + new GasTokenBalancesKey({ + command: { addresses: [{ address, network: "ethereum" }] }, + }); + +const makeRegistry = (layer: Layer.Layer) => + AtomRegistry.make({ + initialValues: [Atom.initialValue(appRuntime.layer, layer)], + }); + +describe("flow balance fact resources", () => { + it("shares equivalent single-Yield and gas-balance requests", () => { + const getSingleYieldBalances = vi.fn(() => + Effect.succeed({ balances: [] }) + ); + const getGasTokenBalances = vi.fn(() => Effect.succeed([])); + const registry = makeRegistry( + Layer.mergeAll( + Layer.succeed(YieldResourceSource, { + getSingleYieldBalances, + } as never), + Layer.succeed(LegacyResourceSource, { getGasTokenBalances } as never) + ) as never + ); + + registry.get(singleYieldBalancesResourceAtom(singleKey())); + registry.get(singleYieldBalancesResourceAtom(singleKey())); + registry.get(gasTokenBalancesResourceAtom(gasKey())); + registry.get(gasTokenBalancesResourceAtom(gasKey())); + + expect(getSingleYieldBalances).toHaveBeenCalledOnce(); + expect(getGasTokenBalances).toHaveBeenCalledOnce(); + }); + + it("keeps distinct wallets and commands in separate facts", () => { + const otherAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" + ); + const getSingleYieldBalances = vi.fn(() => + Effect.succeed({ balances: [] }) + ); + const getGasTokenBalances = vi.fn(() => Effect.succeed([])); + const registry = makeRegistry( + Layer.mergeAll( + Layer.succeed(YieldResourceSource, { + getSingleYieldBalances, + } as never), + Layer.succeed(LegacyResourceSource, { getGasTokenBalances } as never) + ) as never + ); + + registry.get(singleYieldBalancesResourceAtom(singleKey())); + registry.get( + singleYieldBalancesResourceAtom( + new SingleYieldBalancesKey({ + address: otherAddress, + yieldId, + }) + ) + ); + registry.get(gasTokenBalancesResourceAtom(gasKey())); + registry.get( + gasTokenBalancesResourceAtom( + new GasTokenBalancesKey({ + command: { + addresses: [{ address: otherAddress, network: "base" }], + }, + }) + ) + ); + + expect(getSingleYieldBalances).toHaveBeenCalledTimes(2); + expect(getGasTokenBalances).toHaveBeenCalledTimes(2); + }); + + it("publishes typed failure, retries, and scopes cache to the registry", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "single-yield-balances", + }); + const getSingleYieldBalances = vi.fn(() => + offline ? Effect.fail(requestError) : Effect.succeed({ balances: [] }) + ); + const layer = Layer.succeed(YieldResourceSource, { + getSingleYieldBalances, + } as never) as never; + const registry = makeRegistry(layer); + const key = singleKey(); + const resource = singleYieldBalancesResourceAtom(key); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(SingleYieldBalancesError); + + const attemptsBeforeRetry = getSingleYieldBalances.mock.calls.length; + offline = false; + registry.refresh(resource); + expect(AsyncResult.getOrThrow(registry.get(resource)).balances).toEqual([]); + expect(getSingleYieldBalances).toHaveBeenCalledTimes( + attemptsBeforeRetry + 1 + ); + registry.dispose(); + + const nextRegistry = makeRegistry(layer); + nextRegistry.get(resource); + expect(getSingleYieldBalances).toHaveBeenCalledTimes( + attemptsBeforeRetry + 2 + ); + nextRegistry.dispose(); + }); +}); diff --git a/packages/widget/tests/resources/prices-reward-summaries.test.ts b/packages/widget/tests/resources/prices-reward-summaries.test.ts new file mode 100644 index 000000000..ef9b3a720 --- /dev/null +++ b/packages/widget/tests/resources/prices-reward-summaries.test.ts @@ -0,0 +1,149 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { RewardsAddresses } from "../../src/domain/schema/dashboard-models"; +import { + type PriceRequest, + Prices, +} from "../../src/domain/schema/health-price-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + RewardSummariesError, + RewardSummariesKey, + rewardSummariesResourceAtom, +} from "../../src/resources/reward-summaries/reward-summaries"; +import { + TokenPricesKey, + tokenPricesResourceAtom, +} from "../../src/resources/token-prices/token-prices"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { yieldApiYieldFixture } from "../fixtures"; + +const yieldModel = yieldApiYieldFixture(); +const secondYield = yieldApiYieldFixture({ id: "second-yield" }); +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const addresses = Schema.decodeSync(RewardsAddresses)({ address }); +const token = yieldModel.token; +const otherToken = { ...token, network: "base" as const }; + +const makeRegistry = (source: LegacyResourceSource["Service"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(LegacyResourceSource, source) + ), + ], + }); + +describe("price and reward summary resources", () => { + it("shares semantically equivalent price requests with deterministic tokens", () => { + const getPrices = vi.fn((_request: PriceRequest) => + Effect.succeed(new Prices(new Map())) + ); + const registry = makeRegistry( + LegacyResourceSource.of({ getPrices } as never) + ); + const first = new TokenPricesKey({ + currency: "USD", + tokenList: [token, otherToken, token], + }); + const equivalent = new TokenPricesKey({ + currency: "USD", + tokenList: [otherToken, token], + }); + + registry.get(tokenPricesResourceAtom(first)); + registry.get(tokenPricesResourceAtom(equivalent)); + + expect(getPrices).toHaveBeenCalledOnce(); + expect(getPrices.mock.calls[0]?.[0].tokenList).toEqual( + first.request.tokenList + ); + }); + + it("returns empty prices without backend work", () => { + const getPrices = vi.fn(() => Effect.die("unused")); + const registry = makeRegistry( + LegacyResourceSource.of({ getPrices } as never) + ); + + const prices = AsyncResult.getOrThrow( + registry.get( + tokenPricesResourceAtom( + new TokenPricesKey({ currency: "USD", tokenList: [] }) + ) + ) + ); + + expect(prices.value.size).toBe(0); + expect(getPrices).not.toHaveBeenCalled(); + }); + + it("canonicalizes reward IDs and represents missing summaries explicitly", () => { + const getRewardsSummaries = vi.fn(() => + Effect.succeed({ [yieldModel.id]: { rewards: {}, token } } as never) + ); + const registry = makeRegistry( + LegacyResourceSource.of({ getRewardsSummaries } as never) + ); + const key = new RewardSummariesKey({ + addresses, + yieldIds: [secondYield.id, yieldModel.id, yieldModel.id], + }); + + const summaries = AsyncResult.getOrThrow( + registry.get(rewardSummariesResourceAtom(key)) + ); + registry.get( + rewardSummariesResourceAtom( + new RewardSummariesKey({ + addresses, + yieldIds: [yieldModel.id, secondYield.id], + }) + ) + ); + + expect(getRewardsSummaries).toHaveBeenCalledOnce(); + expect(Object.keys(summaries)).toEqual([yieldModel.id, secondYield.id]); + expect(summaries[secondYield.id]).toBeNull(); + }); + + it("publishes typed reward failure and retries bounded work", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-rewards-summary", + }); + const getRewardsSummaries = vi.fn(() => + offline ? Effect.fail(requestError) : Effect.succeed({}) + ); + const registry = makeRegistry( + LegacyResourceSource.of({ getRewardsSummaries } as never) + ); + const key = new RewardSummariesKey({ + addresses, + yieldIds: [yieldModel.id], + }); + const resource = rewardSummariesResourceAtom(key); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(RewardSummariesError); + + const attemptsBeforeRetry = getRewardsSummaries.mock.calls.length; + offline = false; + registry.refresh(resource); + expect( + AsyncResult.getOrThrow(registry.get(resource))[yieldModel.id] + ).toBeNull(); + expect(getRewardsSummaries).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); +}); diff --git a/packages/widget/tests/resources/token-balances.test.ts b/packages/widget/tests/resources/token-balances.test.ts new file mode 100644 index 000000000..4411ddc6e --- /dev/null +++ b/packages/widget/tests/resources/token-balances.test.ts @@ -0,0 +1,161 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import type { TokenBalanceScanCommand } from "../../src/domain/schema/financial-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { tokenBalancesScanAtom as portfolioTokenBalancesAtom } from "../../src/features/portfolio/resources/token-balances"; +import { + refreshTokenBalancesAtom, + TokenBalancesError, + tokenBalancesResourceAtom, +} from "../../src/resources/token-balances/token-balances"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); + +const makeScope = (input?: { + readonly address?: typeof address; + readonly network?: "base" | "ethereum"; +}) => + new WalletScopeKey({ + additionalAddresses: { + lidoStakeAccounts: [], + stakeAccounts: ["stake-account", "stake-account"], + }, + address: input?.address ?? address, + network: input?.network ?? "ethereum", + }); + +const makeRegistry = (source: LegacyResourceSource["Service"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(LegacyResourceSource, source) + ), + ], + }); + +describe("Token Balances resource", () => { + it("shares one acquisition for equivalent Wallet Scope requests", () => { + const scanTokenBalances = vi.fn(() => Effect.succeed([])); + const registry = makeRegistry( + LegacyResourceSource.of({ scanTokenBalances } as never) + ); + + expect( + AsyncResult.getOrThrow( + registry.get(tokenBalancesResourceAtom(makeScope())) + ) + ).toEqual([]); + expect( + AsyncResult.getOrThrow( + registry.get(tokenBalancesResourceAtom(makeScope())) + ) + ).toEqual([]); + expect(scanTokenBalances).toHaveBeenCalledOnce(); + expect(scanTokenBalances).toHaveBeenCalledWith({ + addresses: { + additionalAddresses: { + lidoStakeAccounts: [], + stakeAccounts: ["stake-account"], + }, + address, + }, + network: "ethereum", + }); + }); + + it("does not acquire balances while the current wallet scope is empty", () => { + const scanTokenBalances = vi.fn(() => Effect.succeed([])); + const registry = makeRegistry( + LegacyResourceSource.of({ scanTokenBalances } as never) + ); + + const state = registry.get(portfolioTokenBalancesAtom); + + expect(state.enabled).toBe(false); + expect(AsyncResult.getOrThrow(state.result)).toEqual([]); + expect(scanTokenBalances).not.toHaveBeenCalled(); + }); + + it("publishes typed failures and recovers after retry", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "token-balances-scan", + }); + const scanTokenBalances = vi.fn((_command: TokenBalanceScanCommand) => + offline ? Effect.fail(requestError) : Effect.succeed([]) + ); + const registry = makeRegistry( + LegacyResourceSource.of({ scanTokenBalances } as never) + ); + const scope = makeScope(); + const resource = tokenBalancesResourceAtom(scope); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + const error = Option.getOrThrow(Cause.findErrorOption(failed.cause)); + expect(error).toBeInstanceOf(TokenBalancesError); + expect(error.cause).toBe(requestError); + + const attemptsBeforeRetry = scanTokenBalances.mock.calls.length; + offline = false; + registry.set(refreshTokenBalancesAtom(scope), undefined); + + expect(AsyncResult.getOrThrow(registry.get(resource))).toEqual([]); + expect(scanTokenBalances).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); + + it("keeps distinct Wallet Scope identities in separate resources", () => { + const otherAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" + ); + const scanTokenBalances = vi.fn((_command: TokenBalanceScanCommand) => + Effect.succeed([]) + ); + const registry = makeRegistry( + LegacyResourceSource.of({ scanTokenBalances } as never) + ); + + registry.get(tokenBalancesResourceAtom(makeScope())); + registry.get( + tokenBalancesResourceAtom( + makeScope({ address: otherAddress, network: "base" }) + ) + ); + + expect(scanTokenBalances).toHaveBeenCalledTimes(2); + expect( + scanTokenBalances.mock.calls.map(([command]) => ({ + address: command.addresses.address, + network: command.network, + })) + ).toEqual([ + { address, network: "ethereum" }, + { address: otherAddress, network: "base" }, + ]); + }); + + it("starts with a fresh cache in a new Widget Instance registry", () => { + const scanTokenBalances = vi.fn(() => Effect.succeed([])); + const source = LegacyResourceSource.of({ scanTokenBalances } as never); + const resource = tokenBalancesResourceAtom(makeScope()); + const firstRegistry = makeRegistry(source); + + expect(AsyncResult.getOrThrow(firstRegistry.get(resource))).toEqual([]); + firstRegistry.dispose(); + + const secondRegistry = makeRegistry(source); + expect(AsyncResult.getOrThrow(secondRegistry.get(resource))).toEqual([]); + expect(scanTokenBalances).toHaveBeenCalledTimes(2); + secondRegistry.dispose(); + }); +}); diff --git a/packages/widget/tests/resources/validator-directory.test.ts b/packages/widget/tests/resources/validator-directory.test.ts new file mode 100644 index 000000000..a68f3b905 --- /dev/null +++ b/packages/widget/tests/resources/validator-directory.test.ts @@ -0,0 +1,326 @@ +import { Cause, Effect, Layer, Option } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { + preferredValidatorsResourceAtom, + ValidatorByAddressKey, + ValidatorsError, + ValidatorsKey, + validatorByAddressAtom, + validatorsPullAtom, +} from "../../src/resources/validator-directory/validator-directory"; +import { + type ValidatorDirectoryRequest, + YieldResourceSource, +} from "../../src/services/api/yield-resource-source"; +import { getPullResultItems } from "../../src/shared/effect/pagination"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; + +const yieldId = yieldApiYieldFixture().id; +const makeValidator = (address: string) => { + const validator = yieldApiValidatorFixture({ address }); + return { ...validator, key: address as never }; +}; + +const makeRegistry = ( + listValidators: YieldResourceSource["Service"]["listValidators"] +) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ listValidators } as never) + ) + ), + ], + }); + +const getValidators = ( + result: Atom.Type> +) => getPullResultItems(result).flatMap((page) => page.items); + +describe("Validator resources", () => { + it("shares normalized discovery and loads one page per Pull", async () => { + const validators = Array.from({ length: 101 }, (_, index) => + makeValidator(`validator-${index}`) + ); + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => + Effect.succeed({ + items: validators.slice(request.offset, request.offset + request.limit), + limit: request.limit, + offset: request.offset, + total: validators.length, + }) + ); + const registry = makeRegistry(listValidators as never); + const first = new ValidatorsKey({ + search: " ", + status: "active", + yieldId, + }); + const equivalent = new ValidatorsKey({ + status: "active", + yieldId, + }); + const firstPull = validatorsPullAtom(first); + const equivalentPull = validatorsPullAtom(equivalent); + const unmount = registry.mount(firstPull); + + await vi.waitFor(() => + expect(getValidators(registry.get(firstPull))).toHaveLength(100) + ); + expect(AsyncResult.getOrThrow(registry.get(firstPull)).done).toBe(false); + expect(firstPull).toBe(equivalentPull); + expect(listValidators).toHaveBeenCalledOnce(); + + registry.set(firstPull, undefined); + + await vi.waitFor(() => + expect(getValidators(registry.get(firstPull))).toHaveLength(101) + ); + expect(AsyncResult.getOrThrow(registry.get(firstPull)).done).toBe(true); + expect(listValidators).toHaveBeenCalledTimes(2); + + unmount(); + registry.dispose(); + }); + + it("advances name and address search branches independently", async () => { + const byName = makeValidator("name-match"); + const byAddress = [makeValidator("address-0"), makeValidator("address-1")]; + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => { + if (request.name) { + return Effect.succeed({ + items: [byName], + limit: 1, + offset: request.offset, + total: 1, + }); + } + + return Effect.succeed({ + items: [byAddress[request.offset]!], + limit: 1, + offset: request.offset, + total: 2, + }); + }); + const registry = makeRegistry(listValidators as never); + const pull = validatorsPullAtom( + new ValidatorsKey({ search: "needle", status: "active", yieldId }) + ); + const unmount = registry.mount(pull); + + await vi.waitFor(() => + expect( + getValidators(registry.get(pull)).map((validator) => validator.address) + ).toEqual(["name-match", "address-0"]) + ); + + registry.set(pull, undefined); + + await vi.waitFor(() => + expect( + getValidators(registry.get(pull)).map((validator) => validator.address) + ).toEqual(["name-match", "address-0", "address-1"]) + ); + expect( + listValidators.mock.calls.map(([request]) => ({ + address: request.address, + name: request.name, + offset: request.offset, + })) + ).toEqual([ + { address: undefined, name: "needle", offset: 0 }, + { address: "needle", name: undefined, offset: 0 }, + { address: "needle", name: undefined, offset: 1 }, + ]); + + unmount(); + registry.dispose(); + }); + + it("does not skip a page containing only duplicate validators", async () => { + const first = makeValidator("validator-a"); + const second = makeValidator("validator-b"); + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => + Effect.succeed({ + items: [request.offset < 2 ? first : second], + limit: 1, + offset: request.offset, + total: 3, + }) + ); + const registry = makeRegistry(listValidators as never); + const pull = validatorsPullAtom(new ValidatorsKey({ yieldId })); + const unmount = registry.mount(pull); + + await vi.waitFor(() => + expect(getValidators(registry.get(pull))).toEqual([first]) + ); + expect(listValidators).toHaveBeenCalledOnce(); + + registry.set(pull, undefined); + + await vi.waitFor(() => expect(listValidators).toHaveBeenCalledTimes(2)); + expect(getValidators(registry.get(pull))).toEqual([first]); + + registry.set(pull, undefined); + + await vi.waitFor(() => + expect(getValidators(registry.get(pull))).toEqual([first, second]) + ); + expect(listValidators).toHaveBeenCalledTimes(3); + expect( + listValidators.mock.calls.map(([request]) => request.offset) + ).toEqual([0, 1, 2]); + + unmount(); + registry.dispose(); + }); + + it("retains the first page after a later failure and refreshes from page one", async () => { + const first = makeValidator("validator-a"); + let failSecondPage = true; + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => + request.offset > 0 && failSecondPage + ? Effect.fail( + new ApiRequestError({ + cause: new Error("offline"), + operation: "validator-directory", + }) + ) + : Effect.succeed({ + items: [first], + limit: 1, + offset: request.offset, + total: 2, + }) + ); + const registry = makeRegistry(listValidators as never); + const pull = validatorsPullAtom(new ValidatorsKey({ yieldId })); + const unmount = registry.mount(pull); + + await vi.waitFor(() => + expect(getValidators(registry.get(pull))).toEqual([first]) + ); + registry.set(pull, undefined); + await vi.waitFor(() => + expect(AsyncResult.isFailure(registry.get(pull))).toBe(true) + ); + expect( + registry + .get(pull) + .pipe(AsyncResult.value, Option.getOrThrow) + .items.flatMap((page) => page.items) + ).toEqual([first]); + + failSecondPage = false; + registry.refresh(pull); + + await vi.waitFor(() => + expect(AsyncResult.isSuccess(registry.get(pull))).toBe(true) + ); + expect( + listValidators.mock.calls.map(([request]) => request.offset) + ).toEqual([0, 1, 0]); + + unmount(); + registry.dispose(); + }); + + it("loads the complete preferred set explicitly", () => { + const validators = Array.from({ length: 101 }, (_, index) => + makeValidator(`preferred-${index}`) + ); + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => + Effect.succeed({ + items: validators.slice(request.offset, request.offset + request.limit), + limit: request.limit, + offset: request.offset, + total: validators.length, + }) + ); + const registry = makeRegistry(listValidators as never); + + expect( + AsyncResult.getOrThrow( + registry.get(preferredValidatorsResourceAtom(yieldId)) + ) + ).toHaveLength(101); + expect(listValidators).toHaveBeenCalledTimes(2); + expect( + listValidators.mock.calls.every( + ([request]) => request.preferred === true && request.status === "active" + ) + ).toBe(true); + }); + + it("resolves an address with one bounded request", () => { + const requested = makeValidator("validator-address"); + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => + Effect.succeed({ + items: [requested], + limit: request.limit, + offset: request.offset, + total: 200, + }) + ); + const registry = makeRegistry(listValidators as never); + + expect( + AsyncResult.getOrThrow( + registry.get( + validatorByAddressAtom( + new ValidatorByAddressKey({ + address: requested.address as never, + yieldId, + }) + ) + ) + ).address + ).toBe(requested.address); + expect(listValidators).toHaveBeenCalledOnce(); + expect(listValidators.mock.calls[0]?.[0]).toMatchObject({ + address: requested.address, + offset: 0, + }); + }); + + it("publishes a typed failure and refreshes the exact discovery", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "validator-directory", + }); + const listValidators = vi.fn((request: ValidatorDirectoryRequest) => + offline + ? Effect.fail(requestError) + : Effect.succeed({ + items: [], + limit: request.limit, + offset: request.offset, + total: 0, + }) + ); + const registry = makeRegistry(listValidators); + const resource = validatorsPullAtom(new ValidatorsKey({ yieldId })); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(ValidatorsError); + + const attemptsBeforeRetry = listValidators.mock.calls.length; + offline = false; + registry.refresh(resource); + expect(getValidators(registry.get(resource))).toEqual([]); + expect(listValidators).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); +}); diff --git a/packages/widget/tests/resources/widget-health.test.ts b/packages/widget/tests/resources/widget-health.test.ts new file mode 100644 index 000000000..8a62127a6 --- /dev/null +++ b/packages/widget/tests/resources/widget-health.test.ts @@ -0,0 +1,93 @@ +import { Cause, DateTime, Effect, Layer, Option } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { + underMaintenanceAtom, + WidgetHealthError, + widgetHealthResourceAtom, +} from "../../src/resources/widget-health/widget-health"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; + +const makeRegistry = (getHealth: YieldResourceSource["Service"]["getHealth"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ getHealth } as never) + ) + ), + ], + }); + +describe("Widget Health resource", () => { + it("projects healthy and maintenance states", () => { + let status: "FAIL" | "OK" = "OK"; + const getHealth = vi.fn(() => + Effect.succeed({ status, timestamp: DateTime.makeUnsafe(0) }) + ); + const registry = makeRegistry(getHealth); + + expect(registry.get(underMaintenanceAtom)).toBe(false); + status = "FAIL"; + registry.refresh(widgetHealthResourceAtom); + expect(registry.get(underMaintenanceAtom)).toBe(true); + }); + + it("publishes typed transport failure and recovers on retry", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-api-health", + }); + const getHealth = vi.fn(() => + offline + ? Effect.fail(requestError) + : Effect.succeed({ + status: "OK" as const, + timestamp: DateTime.makeUnsafe(0), + }) + ); + const registry = makeRegistry(getHealth); + const failed = registry.get(widgetHealthResourceAtom); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(WidgetHealthError); + expect(registry.get(underMaintenanceAtom)).toBe(true); + + offline = false; + registry.refresh(widgetHealthResourceAtom); + expect(registry.get(underMaintenanceAtom)).toBe(false); + }); + + it("polls while mounted and stops after Widget Instance disposal", async () => { + vi.useFakeTimers(); + try { + const getHealth = vi.fn(() => + Effect.succeed({ + status: "OK" as const, + timestamp: DateTime.makeUnsafe(0), + }) + ); + const registry = makeRegistry(getHealth); + const unmount = registry.mount(widgetHealthResourceAtom); + + expect(getHealth).toHaveBeenCalledOnce(); + await vi.advanceTimersByTimeAsync(30_000); + expect(getHealth).toHaveBeenCalledTimes(2); + + unmount(); + registry.dispose(); + await vi.advanceTimersByTimeAsync(30_000); + expect(getHealth).toHaveBeenCalledTimes(2); + } finally { + vi.useRealTimers(); + } + }); +}); diff --git a/packages/widget/tests/resources/yield-directory.test.ts b/packages/widget/tests/resources/yield-directory.test.ts new file mode 100644 index 000000000..4ff5b9f0c --- /dev/null +++ b/packages/widget/tests/resources/yield-directory.test.ts @@ -0,0 +1,369 @@ +import { Cause, Deferred, Effect, Layer, Option } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { availableYieldCategoriesAtom } from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { AvailableYieldCategoriesKey } from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { + enrichedYieldDirectoryResourceAtom, + YieldDirectoryError, + YieldDirectoryKey, + yieldDirectoryResourceAtom, +} from "../../src/resources/yield-directory/yield-directory"; +import { + type YieldDirectoryRequest, + YieldResourceSource, +} from "../../src/services/api/yield-resource-source"; +import { API_MAX_PAGE_SIZE } from "../../src/shared/effect/pagination"; +import { yieldApiProviderFixture, yieldApiYieldFixture } from "../fixtures"; + +const makeYield = (id: string, type: "lending" | "staking" = "staking") => { + const base = yieldApiYieldFixture({ id }); + return { + ...base, + mechanics: { ...base.mechanics, type }, + }; +}; + +const makeRegistry = (source: YieldResourceSource["Service"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, source) + ), + ], + }); + +describe("Yield Directory resource", () => { + it("normalizes equivalent filters and chunks explicit Yield IDs", () => { + const yields = Array.from({ length: 101 }, (_, index) => + makeYield(`yield-${index}`) + ); + const yieldsById = new Map( + yields.map((yieldModel) => [yieldModel.id, yieldModel]) + ); + const listYields = vi.fn((request: YieldDirectoryRequest) => { + const requested = (request.yieldIds ?? []).flatMap((yieldId) => { + const yieldModel = yieldsById.get(yieldId); + return yieldModel ? [yieldModel] : []; + }); + + return Effect.succeed({ + items: requested.slice(request.offset, request.offset + request.limit), + limit: request.limit, + offset: request.offset, + total: requested.length, + }); + }); + const registry = makeRegistry( + YieldResourceSource.of({ listYields } as never) + ); + const first = new YieldDirectoryKey({ + network: "ethereum", + types: ["staking", "staking"], + yieldIds: yields.map(({ id }) => id), + }); + const equivalent = new YieldDirectoryKey({ + network: "ethereum", + types: ["staking"], + yieldIds: [...yields.map(({ id }) => id), yields[0]!.id].reverse(), + }); + + expect( + AsyncResult.getOrThrow(registry.get(yieldDirectoryResourceAtom(first))) + .items + ).toHaveLength(101); + expect( + AsyncResult.getOrThrow( + registry.get(yieldDirectoryResourceAtom(equivalent)) + ).items + ).toHaveLength(101); + expect(listYields).toHaveBeenCalledTimes(2); + expect(listYields.mock.calls.map(([request]) => request.offset)).toEqual([ + 0, 0, + ]); + expect( + listYields.mock.calls.map(([request]) => request.yieldIds?.length) + ).toEqual([100, 1]); + }); + + it("skips empty ID sets and distinguishes explicit directories", () => { + const listYields = vi.fn((request: YieldDirectoryRequest) => + Effect.succeed({ + items: [], + limit: request.limit, + offset: request.offset, + total: 0, + }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ listYields } as never) + ); + + registry.get( + yieldDirectoryResourceAtom(new YieldDirectoryKey({ yieldIds: [] })) + ); + registry.get( + yieldDirectoryResourceAtom( + new YieldDirectoryKey({ + yieldIds: [makeYield("yield-a").id, makeYield("yield-a").id], + }) + ) + ); + registry.get( + yieldDirectoryResourceAtom( + new YieldDirectoryKey({ yieldIds: [makeYield("yield-a").id] }) + ) + ); + registry.get( + yieldDirectoryResourceAtom( + new YieldDirectoryKey({ yieldIds: [makeYield("yield-b").id] }) + ) + ); + + expect(listYields).toHaveBeenCalledTimes(2); + expect(listYields.mock.calls.map(([request]) => request.yieldIds)).toEqual([ + ["yield-a"], + ["yield-b"], + ]); + }); + + it("issues one max-size Yield request per category", () => { + const listYields = vi.fn((request: YieldDirectoryRequest) => { + const getItems = (): Array> => { + if (request.types?.includes("staking")) return [makeYield("stake")]; + if (request.types?.includes("lending")) { + return [makeYield("defi", "lending")]; + } + return []; + }; + const items = getItems(); + + return Effect.succeed({ + items, + limit: request.limit, + offset: request.offset, + total: items.length, + }); + }); + const registry = makeRegistry( + YieldResourceSource.of({ listYields } as never) + ); + const categories = registry.get( + availableYieldCategoriesAtom( + new AvailableYieldCategoriesKey({ + categoryOrder: ["stake", "defi", "rwa"], + network: "ethereum", + }) + ) + ); + + expect(AsyncResult.getOrThrow(categories)).toEqual(["stake", "defi"]); + expect(listYields).toHaveBeenCalledTimes(3); + expect( + listYields.mock.calls.map(([request]) => ({ + limit: request.limit, + network: request.network, + offset: request.offset, + })) + ).toEqual([ + { limit: API_MAX_PAGE_SIZE, network: "ethereum", offset: 0 }, + { limit: API_MAX_PAGE_SIZE, network: "ethereum", offset: 0 }, + { limit: API_MAX_PAGE_SIZE, network: "ethereum", offset: 0 }, + ]); + expect( + listYields.mock.calls.every( + ([request]) => request.types !== undefined && request.types.length > 0 + ) + ).toBe(true); + }); + + it("deduplicates provider enrichment through the provider resource", () => { + const yields = [makeYield("yield-a"), makeYield("yield-b")]; + const listYields = vi.fn((request: YieldDirectoryRequest) => + Effect.succeed({ + items: yields, + limit: request.limit, + offset: request.offset, + total: yields.length, + }) + ); + const getProvider = vi.fn(() => + Effect.succeed(Option.some(yieldApiProviderFixture())) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getProvider, listYields } as never) + ); + + const enriched = registry.get( + enrichedYieldDirectoryResourceAtom( + new YieldDirectoryKey({ yieldIds: yields.map(({ id }) => id) }) + ) + ); + + expect(AsyncResult.getOrThrow(enriched).items).toHaveLength(2); + expect(AsyncResult.getOrThrow(enriched).missingProviderIds).toEqual([]); + expect(AsyncResult.getOrThrow(enriched).providerFailures).toEqual([]); + expect(getProvider).toHaveBeenCalledOnce(); + }); + + it("reports missing Yield identities separately from provider failures", () => { + const present = makeYield("yield-present"); + const missingId = makeYield("yield-missing").id; + const listYields = vi.fn((request: YieldDirectoryRequest) => + Effect.succeed({ + items: [present], + limit: request.limit, + offset: request.offset, + total: 1, + }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ + getProvider: () => + Effect.fail( + new ApiRequestError({ + cause: new Error("provider unavailable"), + operation: "yield-provider", + }) + ), + listYields, + } as never) + ); + const result = AsyncResult.getOrThrow( + registry.get( + enrichedYieldDirectoryResourceAtom( + new YieldDirectoryKey({ yieldIds: [present.id, missingId] }) + ) + ) + ); + + expect(result.items).toHaveLength(1); + expect(result.items[0]).not.toHaveProperty("provider"); + expect(result.missingYieldIds).toEqual([missingId]); + expect(result.missingProviderIds).toEqual([]); + expect(result.providerFailures).toHaveLength(1); + expect(result.providerFailures[0]?.providerId).toBe(present.providerId); + }); + + it("reports confirmed missing provider identities", () => { + const present = makeYield("yield-present"); + const registry = makeRegistry( + YieldResourceSource.of({ + getProvider: () => Effect.succeedNone, + listYields: (request: YieldDirectoryRequest) => + Effect.succeed({ + items: [present], + limit: request.limit, + offset: request.offset, + total: 1, + }), + } as never) + ); + const result = AsyncResult.getOrThrow( + registry.get( + enrichedYieldDirectoryResourceAtom( + new YieldDirectoryKey({ yieldIds: [present.id] }) + ) + ) + ); + + expect(result.missingProviderIds).toEqual([present.providerId]); + expect(result.providerFailures).toEqual([]); + }); + + it("publishes typed failures and retries the exact directory", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-directory", + }); + const listYields = vi.fn((request: YieldDirectoryRequest) => + offline + ? Effect.fail(requestError) + : Effect.succeed({ + items: [], + limit: request.limit, + offset: request.offset, + total: 0, + }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ listYields } as never) + ); + const key = new YieldDirectoryKey({ + network: "ethereum", + yieldIds: [makeYield("yield-retry").id], + }); + const resource = yieldDirectoryResourceAtom(key); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(YieldDirectoryError); + + const attemptsBeforeRetry = listYields.mock.calls.length; + offline = false; + registry.refresh(resource); + + expect(AsyncResult.getOrThrow(registry.get(resource))).toEqual({ + items: [], + missingYieldIds: ["yield-retry"], + }); + expect(listYields).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); + + it("does not publish an interrupted stale response after refresh", async () => { + const first = await Effect.runPromise( + Deferred.make[]>() + ); + const second = await Effect.runPromise( + Deferred.make[]>() + ); + let request = 0; + const listYields = vi.fn((input: YieldDirectoryRequest) => { + request += 1; + return Deferred.await(request === 1 ? first : second).pipe( + Effect.map((items) => ({ + items, + limit: input.limit, + offset: input.offset, + total: items.length, + })) + ); + }); + const registry = makeRegistry( + YieldResourceSource.of({ listYields } as never) + ); + const freshYield = makeYield("fresh"); + const staleYield = makeYield("stale"); + const key = new YieldDirectoryKey({ + network: "ethereum", + yieldIds: [freshYield.id, staleYield.id], + }); + const resource = yieldDirectoryResourceAtom(key); + const unmount = registry.mount(resource); + + await vi.waitFor(() => expect(listYields).toHaveBeenCalledOnce()); + registry.refresh(resource); + await vi.waitFor(() => expect(listYields).toHaveBeenCalledTimes(2)); + + await Effect.runPromise(Deferred.succeed(second, [freshYield])); + await vi.waitFor(() => + expect(AsyncResult.getOrThrow(registry.get(resource)).items[0]?.id).toBe( + "fresh" + ) + ); + await Effect.runPromise(Deferred.succeed(first, [staleYield])); + expect(AsyncResult.getOrThrow(registry.get(resource)).items[0]?.id).toBe( + "fresh" + ); + + unmount(); + registry.dispose(); + }); +}); diff --git a/packages/widget/tests/resources/yield-insights.test.ts b/packages/widget/tests/resources/yield-insights.test.ts new file mode 100644 index 000000000..2f951d416 --- /dev/null +++ b/packages/widget/tests/resources/yield-insights.test.ts @@ -0,0 +1,121 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { + YieldHistoryResourceKey, + YieldInsightError, + YieldKycStatusKey, + yieldKycStatusResourceAtom, + yieldRewardRateHistoryResourceAtom, + yieldTvlHistoryResourceAtom, +} from "../../src/resources/yield-insights/yield-insights"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { yieldApiYieldFixture } from "../fixtures"; + +const yieldId = yieldApiYieldFixture().id; +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); +const otherAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" +); + +const makeRegistry = (source: YieldResourceSource["Service"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, source) + ), + ], + }); + +describe("Yield insight resources", () => { + it("shares exact KYC owners and separates wallet addresses", () => { + const getKycStatus = vi.fn(() => + Effect.succeed({ kycStatus: "approved" as const }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getKycStatus } as never) + ); + const key = new YieldKycStatusKey({ address, yieldId }); + + registry.get(yieldKycStatusResourceAtom(key)); + registry.get( + yieldKycStatusResourceAtom(new YieldKycStatusKey({ address, yieldId })) + ); + registry.get( + yieldKycStatusResourceAtom( + new YieldKycStatusKey({ address: otherAddress, yieldId }) + ) + ); + + expect(getKycStatus).toHaveBeenCalledTimes(2); + }); + + it("uses Yield, period, and interval as complete history identity", () => { + const getRewardRateHistory = vi.fn(() => Effect.succeed({} as never)); + const getTvlHistory = vi.fn(() => Effect.succeed({} as never)); + const registry = makeRegistry( + YieldResourceSource.of({ + getRewardRateHistory, + getTvlHistory, + } as never) + ); + const daily = new YieldHistoryResourceKey({ + interval: "day", + period: "30d", + yieldId, + }); + const weekly = new YieldHistoryResourceKey({ + interval: "week", + period: "1y", + yieldId, + }); + + registry.get(yieldRewardRateHistoryResourceAtom(daily)); + registry.get(yieldRewardRateHistoryResourceAtom(daily)); + registry.get(yieldRewardRateHistoryResourceAtom(weekly)); + registry.get(yieldTvlHistoryResourceAtom(daily)); + registry.get(yieldTvlHistoryResourceAtom(weekly)); + + expect(getRewardRateHistory).toHaveBeenCalledTimes(2); + expect(getTvlHistory).toHaveBeenCalledTimes(2); + }); + + it("publishes typed KYC failure and retries the same owner", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-kyc-status", + }); + const getKycStatus = vi.fn(() => + offline + ? Effect.fail(requestError) + : Effect.succeed({ kycStatus: "approved" as const }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getKycStatus } as never) + ); + const key = new YieldKycStatusKey({ address, yieldId }); + const resource = yieldKycStatusResourceAtom(key); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(YieldInsightError); + + const attemptsBeforeRetry = getKycStatus.mock.calls.length; + offline = false; + registry.refresh(resource); + expect(AsyncResult.getOrThrow(registry.get(resource))).toEqual({ + kycStatus: "approved", + }); + expect(getKycStatus).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); +}); diff --git a/packages/widget/tests/resources/yield-opportunity-provider.test.ts b/packages/widget/tests/resources/yield-opportunity-provider.test.ts new file mode 100644 index 000000000..88bbd3808 --- /dev/null +++ b/packages/widget/tests/resources/yield-opportunity-provider.test.ts @@ -0,0 +1,162 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { YieldId } from "../../src/domain/schema/identifiers"; +import { initYieldAtom } from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { InitYieldKey } from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { + YieldOpportunityKey, + yieldOpportunityAtom, +} from "../../src/resources/yield-opportunity/provider"; +import { + YieldOpportunityError, + yieldOpportunityResourceAtom, +} from "../../src/resources/yield-opportunity/yield-opportunity"; +import { + YieldProviderError, + yieldProviderResourceAtom, +} from "../../src/resources/yield-provider/yield-provider"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { yieldApiYieldFixture } from "../fixtures"; + +const yieldModel = yieldApiYieldFixture(); +const yieldId = Schema.decodeSync(YieldId)(yieldModel.id); + +const makeSource = () => { + const getOpportunity = vi.fn(() => Effect.succeed(yieldModel)); + const getProvider = vi.fn(() => + Effect.succeed( + Option.some({ + id: yieldModel.providerId, + name: "Provider", + } as never) + ) + ); + + return { + getOpportunity, + getProvider, + source: YieldResourceSource.of({ getOpportunity, getProvider } as never), + }; +}; + +const makeRegistry = (source: YieldResourceSource["Service"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, source) + ), + ], + }); + +describe("Yield opportunity and provider resources", () => { + it("shares one opportunity between ordinary and initialization projections", () => { + const { getOpportunity, getProvider, source } = makeSource(); + const registry = makeRegistry(source); + + const ordinary = registry.get( + yieldOpportunityAtom(new YieldOpportunityKey({ yieldId })) + ); + const initial = registry.get(initYieldAtom(new InitYieldKey({ yieldId }))); + + expect(AsyncResult.getOrThrow(ordinary)?.id).toBe(yieldId); + expect(AsyncResult.getOrThrow(initial)?.id).toBe(yieldId); + expect(getOpportunity).toHaveBeenCalledOnce(); + expect(getProvider).toHaveBeenCalledOnce(); + }); + + it("shares equivalent direct opportunity and provider requests", () => { + const { getOpportunity, getProvider, source } = makeSource(); + const registry = makeRegistry(source); + + registry.get(yieldOpportunityResourceAtom(yieldId)); + registry.get(yieldOpportunityResourceAtom(yieldId)); + registry.get(yieldProviderResourceAtom(yieldModel.providerId)); + registry.get(yieldProviderResourceAtom(yieldModel.providerId)); + + expect(getOpportunity).toHaveBeenCalledOnce(); + expect(getProvider).toHaveBeenCalledOnce(); + }); + + it("keeps provider failure typed in the enriched opportunity", () => { + const requestError = new ApiRequestError({ + cause: new Error("missing provider"), + operation: "yield-provider", + }); + const getOpportunity = vi.fn(() => Effect.succeed(yieldModel)); + const getProvider = vi.fn(() => Effect.fail(requestError)); + const source = YieldResourceSource.of({ + getOpportunity, + getProvider, + } as never); + const registry = makeRegistry(source); + + const enriched = registry.get( + yieldOpportunityAtom(new YieldOpportunityKey({ yieldId })) + ); + const provider = registry.get( + yieldProviderResourceAtom(yieldModel.providerId) + ); + + expect(AsyncResult.isFailure(enriched)).toBe(true); + if (!AsyncResult.isFailure(enriched)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(enriched.cause)) + ).toBeInstanceOf(YieldProviderError); + expect(AsyncResult.isFailure(provider)).toBe(true); + if (!AsyncResult.isFailure(provider)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(provider.cause)) + ).toBeInstanceOf(YieldProviderError); + }); + + it("models a confirmed missing provider without failing the base opportunity", () => { + const getOpportunity = vi.fn(() => Effect.succeed(yieldModel)); + const getProvider = vi.fn(() => Effect.succeedNone); + const registry = makeRegistry( + YieldResourceSource.of({ getOpportunity, getProvider } as never) + ); + + const enriched = registry.get( + yieldOpportunityAtom(new YieldOpportunityKey({ yieldId })) + ); + const provider = registry.get( + yieldProviderResourceAtom(yieldModel.providerId) + ); + + expect(AsyncResult.getOrThrow(enriched)).not.toHaveProperty("provider"); + expect(Option.isNone(AsyncResult.getOrThrow(provider))).toBe(true); + }); + + it("recovers an opportunity after explicit retry", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-opportunity", + }); + const getOpportunity = vi.fn(() => + offline ? Effect.fail(requestError) : Effect.succeed(yieldModel) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getOpportunity } as never) + ); + const resource = yieldOpportunityResourceAtom(yieldId); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + expect( + Option.getOrThrow(Cause.findErrorOption(failed.cause)) + ).toBeInstanceOf(YieldOpportunityError); + + const attemptsBeforeRetry = getOpportunity.mock.calls.length; + offline = false; + registry.refresh(resource); + + expect(AsyncResult.getOrThrow(registry.get(resource)).id).toBe(yieldId); + expect(getOpportunity).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); +}); diff --git a/packages/widget/tests/resources/yield-positions.test.ts b/packages/widget/tests/resources/yield-positions.test.ts new file mode 100644 index 000000000..f2f0dea39 --- /dev/null +++ b/packages/widget/tests/resources/yield-positions.test.ts @@ -0,0 +1,334 @@ +import { Cause, Effect, Layer, Option, Schema } from "effect"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import * as Reactivity from "effect/unstable/reactivity/Reactivity"; +import { describe, expect, it, vi } from "vitest"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { ApiRequestError } from "../../src/domain/schema/api-errors"; +import { EarnPosition } from "../../src/domain/schema/earn-models"; +import type { YieldBalancesCommand } from "../../src/domain/schema/financial-models"; +import { WalletAddress } from "../../src/domain/schema/identifiers"; +import { positionsDataAtom as earnPositionsDataAtom } from "../../src/features/earn/state/atoms-state/catalog/atoms"; +import { PositionsDataKey } from "../../src/features/earn/state/atoms-state/catalog/keys"; +import { + PositionDataKey, + positionDataAtom, +} from "../../src/features/portfolio/resources/positions"; +import { + refreshYieldPositionsAtom, + YieldPositionsError, + yieldPositionsResourceAtom, +} from "../../src/resources/yield-positions/yield-positions"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { resourceInvalidationKeys } from "../../src/services/resource-invalidation"; +import { WalletScopeKey } from "../../src/services/wallet/domain/scope"; +import { yieldApiYieldFixture, yieldBalanceFixture } from "../fixtures"; + +const address = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000001" +); + +const makeScope = () => + new WalletScopeKey({ + address, + network: "ethereum", + }); + +const makePosition = (amount = "2") => { + const yieldDto = yieldApiYieldFixture(); + + return Schema.decodeUnknownSync(EarnPosition)({ + balances: [ + yieldBalanceFixture({ + amount, + amountUsd: amount, + type: "active", + token: yieldDto.token, + }), + ], + outputTokenBalance: null, + yieldId: yieldDto.id, + }); +}; + +const makeRegistry = (source: YieldResourceSource["Service"]) => + AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.succeed(YieldResourceSource, source) + ), + ], + }); + +const reactivityAtom = appRuntime.atom( + Effect.gen(function* () { + return yield* Reactivity.Reactivity; + }) +); + +describe("Yield Positions resource", () => { + it("shares one acquisition for equivalent Wallet Scope requests", () => { + const getPositions = vi.fn((_command: YieldBalancesCommand) => + Effect.succeed({ errors: [], items: [makePosition()] }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ + getPositions, + } as never) + ); + + const first = registry.get(yieldPositionsResourceAtom(makeScope())); + const second = registry.get(yieldPositionsResourceAtom(makeScope())); + + expect(AsyncResult.getOrThrow(first).items).toHaveLength(1); + expect(AsyncResult.getOrThrow(second).items).toHaveLength(1); + expect(getPositions).toHaveBeenCalledOnce(); + }); + + it("ignores additional addresses that do not affect the request", () => { + const getPositions = vi.fn((_command: YieldBalancesCommand) => + Effect.succeed({ errors: [], items: [makePosition()] }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getPositions } as never) + ); + const baseScope = makeScope(); + const scopeWithAdditionalAddress = new WalletScopeKey({ + additionalAddresses: { binanceBeaconAddress: "bnb-address" }, + address, + network: "ethereum", + }); + + const first = yieldPositionsResourceAtom(baseScope); + const second = yieldPositionsResourceAtom(scopeWithAdditionalAddress); + + expect(second).toBe(first); + expect(AsyncResult.getOrThrow(registry.get(first)).items).toHaveLength(1); + expect(AsyncResult.getOrThrow(registry.get(second)).items).toHaveLength(1); + expect(getPositions).toHaveBeenCalledOnce(); + }); + + it("shares EVM owner requests across equivalent address casing", () => { + const checksumAddress = Schema.decodeSync(WalletAddress)( + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ); + const lowercaseAddress = Schema.decodeSync(WalletAddress)( + checksumAddress.toLowerCase() + ); + const getPositions = vi.fn((_command: YieldBalancesCommand) => + Effect.succeed({ errors: [], items: [makePosition()] }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getPositions } as never) + ); + const checksumScope = new WalletScopeKey({ + address: checksumAddress, + network: "ethereum", + }); + const lowercaseScope = new WalletScopeKey({ + address: lowercaseAddress, + network: "ethereum", + }); + + const first = yieldPositionsResourceAtom(checksumScope); + const second = yieldPositionsResourceAtom(lowercaseScope); + + expect(second).toBe(first); + registry.get(first); + registry.get(second); + expect(getPositions).toHaveBeenCalledOnce(); + expect(getPositions.mock.calls[0]?.[0].queries[0]?.address).toBe( + lowercaseAddress + ); + }); + + it("shares one acquisition between Earn and Portfolio projections", () => { + const position = makePosition(); + const getPositions = vi.fn(() => + Effect.succeed({ errors: [], items: [position] }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getPositions } as never) + ); + const scope = makeScope(); + + const earnPositions = registry.get( + earnPositionsDataAtom(new PositionsDataKey({ scope })) + ); + const portfolioPosition = registry.get( + positionDataAtom( + new PositionDataKey({ + scope, + yieldId: position.yieldId, + }) + ) + ); + + expect(AsyncResult.getOrThrow(earnPositions).size).toBe(1); + expect(AsyncResult.getOrThrow(portfolioPosition)?.yieldId).toBe( + position.yieldId + ); + expect(getPositions).toHaveBeenCalledOnce(); + }); + + it("publishes a stable failure and recovers when the resource is retried", () => { + let offline = true; + const requestError = new ApiRequestError({ + cause: new Error("offline"), + operation: "yield-positions", + }); + const getPositions = vi.fn(() => { + return offline + ? Effect.fail(requestError) + : Effect.succeed({ errors: [], items: [makePosition()] }); + }); + const registry = makeRegistry( + YieldResourceSource.of({ getPositions } as never) + ); + const resource = yieldPositionsResourceAtom(makeScope()); + const failed = registry.get(resource); + + expect(AsyncResult.isFailure(failed)).toBe(true); + if (!AsyncResult.isFailure(failed)) throw new Error("Expected failure"); + const error = Option.getOrThrow(Cause.findErrorOption(failed.cause)); + expect(error).toBeInstanceOf(YieldPositionsError); + expect(error.cause).toBe(requestError); + + const attemptsBeforeRetry = getPositions.mock.calls.length; + offline = false; + registry.set(refreshYieldPositionsAtom(makeScope()), undefined); + + expect(AsyncResult.getOrThrow(registry.get(resource)).items).toHaveLength( + 1 + ); + expect(getPositions).toHaveBeenCalledTimes(attemptsBeforeRetry + 1); + }); + + it("keeps distinct Wallet Scope identities in separate resources", () => { + const otherAddress = Schema.decodeSync(WalletAddress)( + "0x0000000000000000000000000000000000000002" + ); + const getPositions = vi.fn((_command: YieldBalancesCommand) => + Effect.succeed({ errors: [], items: [makePosition()] }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getPositions } as never) + ); + + registry.get(yieldPositionsResourceAtom(makeScope())); + registry.get( + yieldPositionsResourceAtom( + new WalletScopeKey({ address: otherAddress, network: "base" }) + ) + ); + + expect(getPositions).toHaveBeenCalledTimes(2); + expect( + getPositions.mock.calls.map(([command]) => command.queries[0]) + ).toEqual([ + { address, network: "ethereum" }, + { address: otherAddress, network: "base" }, + ]); + }); + + it("starts with a fresh cache in a new Widget Instance registry", () => { + const getPositions = vi.fn(() => + Effect.succeed({ errors: [], items: [makePosition()] }) + ); + const source = YieldResourceSource.of({ getPositions } as never); + const resource = yieldPositionsResourceAtom(makeScope()); + const firstRegistry = makeRegistry(source); + + expect( + AsyncResult.getOrThrow(firstRegistry.get(resource)).items + ).toHaveLength(1); + firstRegistry.dispose(); + + const secondRegistry = makeRegistry(source); + expect( + AsyncResult.getOrThrow(secondRegistry.get(resource)).items + ).toHaveLength(1); + expect(getPositions).toHaveBeenCalledTimes(2); + secondRegistry.dispose(); + }); + + it("refreshes the matching resource after semantic invalidation", async () => { + let amount = "2"; + const getPositions = vi.fn(() => + Effect.succeed({ errors: [], items: [makePosition(amount)] }) + ); + const registry = AtomRegistry.make({ + initialValues: [ + Atom.initialValue( + appRuntime.layer, + Layer.mergeAll( + Reactivity.layer, + Layer.succeed( + YieldResourceSource, + YieldResourceSource.of({ getPositions } as never) + ) + ) as never + ), + ], + }); + const scope = makeScope(); + const resource = yieldPositionsResourceAtom(scope); + const unmountResource = registry.mount(resource); + const unmountReactivity = registry.mount(reactivityAtom); + + await vi.waitFor(() => + expect( + AsyncResult.getOrThrow( + registry.get(resource) + ).items[0]?.balances[0]?.amount.toFixed() + ).toBe("2") + ); + + amount = "7"; + const reactivity = AsyncResult.getOrThrow(registry.get(reactivityAtom)); + await Effect.runPromise( + reactivity.withBatch( + reactivity.invalidate(resourceInvalidationKeys.yieldPositions(scope)) + ) + ); + + await vi.waitFor(() => + expect( + AsyncResult.getOrThrow( + registry.get(resource) + ).items[0]?.balances[0]?.amount.toFixed() + ).toBe("7") + ); + expect(getPositions).toHaveBeenCalledTimes(2); + + unmountResource(); + unmountReactivity(); + registry.dispose(); + }); + + it("polls while mounted and stops polling after unmount", async () => { + vi.useFakeTimers(); + try { + const getPositions = vi.fn(() => + Effect.succeed({ errors: [], items: [makePosition()] }) + ); + const registry = makeRegistry( + YieldResourceSource.of({ getPositions } as never) + ); + const resource = yieldPositionsResourceAtom(makeScope()); + const unmount = registry.mount(resource); + + expect(getPositions).toHaveBeenCalledOnce(); + + await vi.advanceTimersByTimeAsync(60_000); + expect(getPositions).toHaveBeenCalledTimes(2); + + unmount(); + await vi.advanceTimersByTimeAsync(60_000); + expect(getPositions).toHaveBeenCalledTimes(2); + registry.dispose(); + } finally { + vi.useRealTimers(); + } + }); +}); diff --git a/packages/widget/tests/services/application-router.test.ts b/packages/widget/tests/services/application-router.test.ts new file mode 100644 index 000000000..1cc0fe6d4 --- /dev/null +++ b/packages/widget/tests/services/application-router.test.ts @@ -0,0 +1,121 @@ +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { describe, expect, it, vi } from "vitest"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import { appRuntime } from "../../src/app/runtime/app-runtime"; +import { applicationRouterAtom } from "../../src/app/runtime/application-router-runtime"; +import { + runWidgetNavigationCommand, + type WidgetNavigationCommand, +} from "../../src/app/runtime/navigation"; +import { toWidgetPath } from "../../src/services/navigation/widget-navigation"; + +const navigationCommandAtom = appRuntime + .fn( + (command: WidgetNavigationCommand) => runWidgetNavigationCommand(command), + { concurrent: false } + ) + .pipe(Atom.withLabel("applicationRouterTestNavigationCommandAtom")); + +const makeRegistry = () => + AtomRegistry.make({ + initialValues: [ + [ + widgetConfigAtom, + normalizeWidgetConfig({ apiKey: "test", variant: "default" }), + ], + ], + }); + +describe("ApplicationRouter runtime", () => { + it("synchronously exposes the memory router on the first registry read", () => { + const registry = makeRegistry(); + + try { + const router = registry.get(applicationRouterAtom); + + expect(router.state.location.pathname).toBe("/"); + expect(router.routes).toHaveLength(1); + } finally { + registry.dispose(); + } + }); + + it("drives real memory history through WidgetNavigation", async () => { + const registry = makeRegistry(); + + try { + const router = registry.get(applicationRouterAtom); + + registry.set(navigationCommandAtom, { + _tag: "Push", + path: toWidgetPath("/review"), + scroll: "preserve", + }); + + await vi.waitFor(() => + expect(router.state.location.pathname).toBe("/review") + ); + + registry.set(navigationCommandAtom, { + _tag: "Replace", + path: toWidgetPath("/complete"), + scroll: "preserve", + }); + + await vi.waitFor(() => + expect(router.state.location.pathname).toBe("/complete") + ); + + registry.set(navigationCommandAtom, { + _tag: "Back", + scroll: "preserve", + }); + + await vi.waitFor(() => expect(router.state.location.pathname).toBe("/")); + } finally { + registry.dispose(); + } + }); + + it("preserves one router within a generation and resets fresh generations", async () => { + const firstRegistry = makeRegistry(); + const secondRegistry = makeRegistry(); + + try { + const firstRouter = firstRegistry.get(applicationRouterAtom); + + firstRegistry.set(navigationCommandAtom, { + _tag: "Push", + path: toWidgetPath("/review"), + scroll: "preserve", + }); + await vi.waitFor(() => + expect(firstRouter.state.location.pathname).toBe("/review") + ); + + expect(firstRegistry.get(applicationRouterAtom)).toBe(firstRouter); + + const secondRouter = secondRegistry.get(applicationRouterAtom); + expect(secondRouter).not.toBe(firstRouter); + expect(secondRouter.state.location.pathname).toBe("/"); + } finally { + firstRegistry.dispose(); + secondRegistry.dispose(); + } + }); + + it("disposes the router exactly once with its registry generation", () => { + const registry = makeRegistry(); + const router = registry.get(applicationRouterAtom); + const dispose = vi.spyOn(router, "dispose"); + + registry.dispose(); + registry.dispose(); + + expect(dispose).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/widget/tests/services/wallet-modal.test.ts b/packages/widget/tests/services/wallet-modal.test.ts new file mode 100644 index 000000000..5394058ae --- /dev/null +++ b/packages/widget/tests/services/wallet-modal.test.ts @@ -0,0 +1,71 @@ +import { Effect } from "effect"; +import { mainnet } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; +import { runAddLedgerAccount } from "../../src/features/wallet/state/workflows"; +import { WalletModal } from "../../src/services/wallet/wallet-modal"; + +describe("WalletModal", () => { + it("keeps modal callbacks runtime-scoped and owner-released", async () => { + const firstClose = vi.fn(); + const secondClose = vi.fn(); + const firstOwner = {}; + const secondOwner = {}; + const program = Effect.gen(function* () { + const modal = yield* WalletModal; + + yield* modal.install(firstOwner, { + closeChain: firstClose, + openConnect: vi.fn(), + }); + yield* modal.install(secondOwner, { + closeChain: secondClose, + openConnect: vi.fn(), + }); + yield* modal.uninstall(firstOwner); + yield* modal.closeChain; + yield* modal.uninstall(secondOwner); + yield* modal.closeChain; + }); + + await Effect.runPromise(program.pipe(Effect.provide(WalletModal.layer))); + + expect(firstClose).not.toHaveBeenCalled(); + expect(secondClose).toHaveBeenCalledOnce(); + }); + + it("closes the chain modal after Ledger switches account", async () => { + const closeChain = vi.fn(); + const owner = {}; + const program = Effect.gen(function* () { + const modal = yield* WalletModal; + yield* modal.install(owner, { + closeChain, + openConnect: vi.fn(), + }); + yield* runAddLedgerAccount({ + chain: mainnet, + connector: { + requestAndSwitchAccount: () => Effect.succeed(mainnet), + }, + }); + }); + + await Effect.runPromise(program.pipe(Effect.provide(WalletModal.layer))); + + expect(closeChain).toHaveBeenCalledOnce(); + }); + + it("retains a typed failure when the Ledger connector is missing", async () => { + const error = await Effect.runPromise( + runAddLedgerAccount({ + chain: mainnet, + connector: null, + }).pipe(Effect.provide(WalletModal.layer), Effect.flip) + ); + + expect(error).toMatchObject({ + _tag: "WalletIntegrationError", + operation: "ledger-add-account", + }); + }); +}); diff --git a/packages/widget/tests/services/widget-navigation.test.ts b/packages/widget/tests/services/widget-navigation.test.ts new file mode 100644 index 000000000..215ce5a6b --- /dev/null +++ b/packages/widget/tests/services/widget-navigation.test.ts @@ -0,0 +1,83 @@ +import { Effect, Layer, Schema } from "effect"; +import type { DataRouter } from "react-router"; +import { describe, expect, it, vi } from "vitest"; +import { ApplicationRouter } from "../../src/services/navigation/application-router"; +import { + toWidgetPath, + WidgetNavigation, + WidgetPath, +} from "../../src/services/navigation/widget-navigation"; + +describe("WidgetNavigation", () => { + it("executes absolute push, replace, back, and scroll decisions", async () => { + const scrollToTop = vi.fn(); + vi.stubGlobal("window", { scrollTo: scrollToTop }); + const applicationRouterLayer = ApplicationRouter.layer([ + { path: "*", Component: () => null }, + ]); + const program = Effect.gen(function* () { + const { router } = yield* ApplicationRouter; + const navigation = yield* WidgetNavigation; + + yield* navigation.push(toWidgetPath("/positions/one"), { + scroll: "reset", + }); + yield* navigation.replace(toWidgetPath("/positions/two"), { + scroll: "preserve", + }); + yield* navigation.back({ scroll: "reset" }); + + return router.state.location.pathname; + }); + + try { + const path = await Effect.runPromise( + program.pipe( + Effect.provide( + Layer.mergeAll( + applicationRouterLayer, + WidgetNavigation.layer().pipe( + Layer.provide(applicationRouterLayer) + ) + ) + ) + ) + ); + + expect(path).toBe("/"); + expect(scrollToTop).toHaveBeenCalledTimes(2); + } finally { + vi.unstubAllGlobals(); + } + }); + + it("rejects route-relative application paths", () => { + expect(() => Schema.decodeUnknownSync(WidgetPath)("positions/one")).toThrow( + 'Expected a string matching template literal parts, got "positions/one"' + ); + }); + + it("normalizes router failures", async () => { + const applicationRouterLayer = Layer.succeed( + ApplicationRouter, + ApplicationRouter.of({ + router: { + navigate: () => + Promise.reject(new Error("router rejected navigation")), + } as unknown as DataRouter, + }) + ); + const program = WidgetNavigation.use((navigation) => + navigation.push(toWidgetPath("/review"), { scroll: "preserve" }) + ).pipe( + Effect.provide( + WidgetNavigation.layer().pipe(Layer.provide(applicationRouterLayer)) + ), + Effect.flip + ); + + expect((await Effect.runPromise(program))._tag).toBe( + "WidgetNavigationError" + ); + }); +}); diff --git a/packages/widget/tests/shared/date.test.ts b/packages/widget/tests/shared/date.test.ts new file mode 100644 index 000000000..d5bd7c6b8 --- /dev/null +++ b/packages/widget/tests/shared/date.test.ts @@ -0,0 +1,57 @@ +import { DateTime, Duration } from "effect"; +import { describe, expect, it } from "vitest"; +import { + getActivityDayKind, + getActivityRelativeTime, + getDisplayDurationUntil, +} from "../../src/shared/lib/date"; + +const utc = (value: string) => DateTime.makeUnsafe(value); + +describe("date presentation helpers", () => { + it("classifies local calendar days across a daylight-saving transition", () => { + const now = utc("2026-03-08T07:30:00.000Z"); + const earlier = utc("2026-03-08T04:30:00.000Z"); + + expect( + getActivityDayKind( + earlier, + now, + DateTime.zoneMakeNamedUnsafe("America/New_York") + ) + ).toBe("yesterday"); + expect( + getActivityDayKind(earlier, now, DateTime.zoneMakeNamedUnsafe("UTC")) + ).toBe("today"); + }); + + it.each([ + ["2026-07-23T11:59:01.000Z", { unit: "now" }], + ["2026-07-23T11:58:00.000Z", { unit: "minutes", value: 2 }], + ["2026-07-23T09:30:00.000Z", { unit: "hours", value: 2 }], + ["2026-07-20T11:00:00.000Z", { unit: "days", value: 3 }], + ] as const)("buckets compact relative time for %s", (date, expected) => { + expect( + getActivityRelativeTime(utc(date), utc("2026-07-23T12:00:00.000Z")) + ).toEqual(expected); + }); + + it.each([ + [Duration.seconds(59), { unit: "less-than-minute" }], + [Duration.minutes(1), { unit: "minutes", value: 1 }], + [Duration.minutes(59), { unit: "minutes", value: 59 }], + [Duration.hours(1), { unit: "hours", value: 1 }], + [Duration.hours(23), { unit: "hours", value: 23 }], + [Duration.days(1), { unit: "days", value: 1 }], + [Duration.days(30), { unit: "days", value: 30 }], + [Duration.days(31), { unit: "months", value: 1 }], + [Duration.days(364), { unit: "months", value: 12 }], + [Duration.days(365), { unit: "years", value: 1 }], + ] as const)("uses agreed display breakpoints for %o", (duration, expected) => { + const now = utc("2026-01-01T00:00:00.000Z"); + + expect( + getDisplayDurationUntil(DateTime.addDuration(now, duration), now) + ).toEqual(expected); + }); +}); diff --git a/packages/widget/tests/shared/formatters.test.ts b/packages/widget/tests/shared/formatters.test.ts new file mode 100644 index 000000000..552e8425e --- /dev/null +++ b/packages/widget/tests/shared/formatters.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { + formatBorrowProviderName, + formatHealthFactor, + formatNetworkName, + formatPercent, + formatUsd, + humanizeEnumValue, +} from "../../src/shared/lib/formatters"; + +describe("shared formatters", () => { + it.each([ + [null, "-"], + [Number.NaN, "-"], + [0, "$0.00"], + [0.004, "<$0.01"], + [-0.004, ">-$0.01"], + [0.01, "$0.01"], + [0.5, "$0.50"], + [2.54, "$2.54"], + [999.99, "$999.99"], + [1000, "$1K"], + [1250, "$1.25K"], + [4_100_000, "$4.1M"], + [-1250, "-$1.25K"], + ])("formats USD value %s as %s", (value, expected) => { + expect(formatUsd(value)).toBe(expected); + }); + + it.each([ + [null, "-"], + [undefined, "-"], + ["", "-"], + [Number.NaN, "-"], + [0, "0%"], + [0.075, "7.5%"], + [0.7523, "75.23%"], + ["0.5", "50%"], + ])("formats percent value %s as %s", (value, expected) => { + expect(formatPercent(value)).toBe(expected); + }); + + it("formats shared borrow presentation values", () => { + expect(formatHealthFactor(4.3708)).toBe("4.3708"); + expect(formatHealthFactor(2.125)).toBe("2.125"); + expect(formatHealthFactor(4.37)).toBe("4.37"); + expect(formatNetworkName("ethereum")).toBe("Ethereum"); + expect(formatNetworkName("arbitrum-one")).toBe("Arbitrum One"); + expect(formatBorrowProviderName("Morpho Blue Borrow")).toBe("Morpho Blue"); + expect(formatBorrowProviderName("Aave V3")).toBe("Aave V3"); + }); + + it("humanizes enum-like values", () => { + expect(humanizeEnumValue("block")).toBe("Block"); + expect(humanizeEnumValue("WITHDRAWAL_REQUEST")).toBe("Withdrawal Request"); + expect(humanizeEnumValue("FOO__BAR")).toBe("Foo Bar"); + }); +}); diff --git a/packages/widget/tests/shared/presentation-clock.test.ts b/packages/widget/tests/shared/presentation-clock.test.ts new file mode 100644 index 000000000..5f30cf5a4 --- /dev/null +++ b/packages/widget/tests/shared/presentation-clock.test.ts @@ -0,0 +1,34 @@ +import { DateTime } from "effect"; +import { AtomRegistry } from "effect/unstable/reactivity"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { presentationClockAtom } from "../../src/shared/effect/presentation-clock"; + +describe("presentation clock", () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it("publishes immediately and refreshes once per minute while observed", async () => { + vi.useFakeTimers(); + const initial = DateTime.makeUnsafe("2026-07-23T12:00:00.000Z"); + vi.setSystemTime(DateTime.toEpochMillis(initial)); + const registry = AtomRegistry.make(); + const unmount = registry.mount(presentationClockAtom); + + await vi.advanceTimersByTimeAsync(0); + const first = registry.get(presentationClockAtom); + expect(first).not.toBeNull(); + expect(first && DateTime.formatIso(first.now)).toBe( + "2026-07-23T12:00:00.000Z" + ); + + await vi.advanceTimersByTimeAsync(60_000); + const second = registry.get(presentationClockAtom); + expect(second && DateTime.formatIso(second.now)).toBe( + "2026-07-23T12:01:00.000Z" + ); + + unmount(); + registry.dispose(); + }); +}); diff --git a/packages/widget/tests/use-cases/deep-links-flow/deep-links-flow.browser.test.tsx b/packages/widget/tests/use-cases/deep-links-flow/deep-links-flow.browser.test.tsx new file mode 100644 index 000000000..e21804835 --- /dev/null +++ b/packages/widget/tests/use-cases/deep-links-flow/deep-links-flow.browser.test.tsx @@ -0,0 +1,237 @@ +import { Array as EArray } from "effect"; +import { numberToHex } from "viem"; +import { APToPercentage } from "../../../src/shared/lib/general"; +import { describe, expect, it } from "../../utils/test-extend"; +import { renderApp } from "../../utils/test-utils"; +import { setup } from "./setup"; + +describe("Deep links flow", () => { + it("Loads app with correct yield opportunity", async ({ worker }) => { + const { + customConnectors, + setUrl, + account, + avaxLiquidStaking, + avaxNativeStaking, + } = await setup(worker); + + setUrl({ accountId: account, yieldId: avaxLiquidStaking.id }); + + const withAvaxLiquidStakingApp = await renderApp({ + wagmi: { __customConnectors__: customConnectors }, + }); + + await expect + .element( + withAvaxLiquidStakingApp + .getByTestId("select-opportunity") + .getByText( + avaxLiquidStaking.metadata.rewardTokens?.[0]?.symbol ?? + avaxLiquidStaking.token.symbol + ) + ) + .toBeInTheDocument(); + + await expect + .element( + withAvaxLiquidStakingApp + .getByText(`${APToPercentage(avaxLiquidStaking.rewardRate)}%`) + .first() + ) + .toBeInTheDocument(); + + await withAvaxLiquidStakingApp.unmount(); + + setUrl({ accountId: account, yieldId: avaxNativeStaking.id }); + + const withAvaxNativeStakingApp = await renderApp({ + wagmi: { __customConnectors__: customConnectors }, + }); + + await expect + .element(withAvaxNativeStakingApp.getByText("Stake").first()) + .toBeInTheDocument(); + + await expect + .element( + withAvaxNativeStakingApp + .getByTestId("select-opportunity") + .getByText(avaxNativeStaking.token.symbol) + ) + .toBeInTheDocument(); + + await expect + .element( + withAvaxNativeStakingApp + .getByText(`${APToPercentage(avaxNativeStaking.rewardRate)}%`) + .first() + ) + .toBeInTheDocument(); + + await withAvaxNativeStakingApp.unmount(); + }); + + it("Works correctly with pending action query param without validator address requirement", async ({ + worker, + }) => { + const { customConnectors, setUrl, account, avaxLiquidStaking, requestFn } = + await setup(worker); + + setUrl({ + accountId: account, + yieldId: avaxLiquidStaking.id, + pendingaction: "CLAIM_REWARDS", + }); + + const app = await renderApp({ + wagmi: { __customConnectors__: customConnectors }, + }); + + await expect + .element(app.getByText("Claim", { exact: true })) + .toBeInTheDocument(); + + await expect + .element( + app.getByText("By clicking confirm you agree to", { exact: false }) + ) + .toBeInTheDocument(); + + const confirmButton = app.getByRole("button", { + exact: true, + name: "Confirm", + }); + + await expect.element(confirmButton).toBeInTheDocument(); + + await confirmButton.click(); + + await expect.element(app.getByText("Follow Steps")).toBeInTheDocument(); + + await expect + .poll(() => requestFn, { timeout: 1000 * 5 }) + .toHaveBeenCalledWith( + { + method: "eth_sendTransaction", + params: expect.anything(), + }, + undefined + ); + + expect(requestFn).toHaveBeenCalledWith( + { method: "eth_chainId" }, + undefined + ); + + await expect + .element(app.getByText("Successfully claimed rewards", { exact: false })) + .toBeInTheDocument(); + + expect(app.getByText("View Claim rewards transaction")).toBeInTheDocument(); + + app.unmount(); + }); + + it("Works correctly with pending action query param with validator address requirement", async ({ + worker, + }) => { + const { customConnectors, setUrl, account, avaxLiquidStaking, requestFn } = + await setup(worker, { withValidatorAddressesRequired: true }); + + setUrl({ + accountId: account, + yieldId: avaxLiquidStaking.id, + pendingaction: "CLAIM_REWARDS", + }); + + const app = await renderApp({ + wagmi: { __customConnectors__: customConnectors }, + }); + + await expect + .element(app.getByText(avaxLiquidStaking.metadata.name)) + .toBeInTheDocument(); + + await expect + .element( + app.getByTestId( + EArray.getUnsafe(avaxLiquidStaking.validators, 0).address + ) + ) + .toBeInTheDocument(); + + await app + .getByTestId(EArray.getUnsafe(avaxLiquidStaking.validators, 0).address) + .click(); + await app.getByText("Submit").click(); + + await expect + .element(app.getByText("Claim", { exact: true })) + .toBeInTheDocument(); + + await expect + .element( + app.getByText("By clicking confirm you agree to", { exact: false }) + ) + .toBeInTheDocument(); + + await expect + .element(app.getByRole("button", { name: "Confirm" })) + .toBeInTheDocument(); + + await app.getByRole("button", { name: "Confirm" }).click(); + + await expect.element(app.getByText("Follow Steps")).toBeInTheDocument(); + + await expect + .poll(() => requestFn, { timeout: 1000 * 5 }) + .toHaveBeenCalledWith( + { + method: "eth_sendTransaction", + params: expect.anything(), + }, + undefined + ); + + expect(requestFn).toHaveBeenCalledWith( + { method: "eth_chainId" }, + undefined + ); + + await expect + .element(app.getByText("Successfully claimed rewards", { exact: false })) + .toBeInTheDocument(); + + await expect + .element(app.getByText("View Claim rewards transaction")) + .toBeInTheDocument(); + + await app.unmount(); + }); + + it("Handles init network correctly", async ({ worker }) => { + const { setUrl, customConnectors, requestFn, getCurrentChainId } = + await setup(worker); + + expect(getCurrentChainId()).not.toBe(1); + setUrl({ network: "ethereum" }); + + const app = await renderApp({ + wagmi: { __customConnectors__: customConnectors }, + }); + + await expect.element(app.getByText("Ethereum")).toBeInTheDocument(); + + await expect + .poll(() => requestFn, { timeout: 1000 * 5 }) + .toHaveBeenCalledWith( + { + method: "wallet_switchEthereumChain", + params: [{ chainId: numberToHex(1) }], + }, + undefined + ); + + await app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/deep-links-flow/deep-links-flow.test.tsx b/packages/widget/tests/use-cases/deep-links-flow/deep-links-flow.test.tsx deleted file mode 100644 index 0bb5a9c5c..000000000 --- a/packages/widget/tests/use-cases/deep-links-flow/deep-links-flow.test.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { numberToHex } from "viem"; -import { APToPercentage } from "../../../src/utils"; -import { describe, expect, it } from "../../utils/test-extend"; -import { renderApp } from "../../utils/test-utils"; -import { setup } from "./setup"; - -describe("Deep links flow", () => { - it("Loads app with correct yield opportunity", async ({ worker }) => { - const { - customConnectors, - setUrl, - account, - avaxLiquidStaking, - avaxNativeStaking, - } = await setup(worker); - - setUrl({ accountId: account, yieldId: avaxLiquidStaking.id }); - - const withAvaxLiquidStakingApp = await renderApp({ - wagmi: { __customConnectors__: customConnectors }, - }); - - await expect - .element( - withAvaxLiquidStakingApp - .getByTestId("select-opportunity") - .getByText( - avaxLiquidStaking.metadata.rewardTokens?.[0]?.symbol ?? - avaxLiquidStaking.token.symbol - ) - ) - .toBeInTheDocument(); - - await expect - .element( - withAvaxLiquidStakingApp - .getByText(`${APToPercentage(avaxLiquidStaking.rewardRate)}%`) - .first() - ) - .toBeInTheDocument(); - - await withAvaxLiquidStakingApp.unmount(); - - setUrl({ accountId: account, yieldId: avaxNativeStaking.id }); - - const withAvaxNativeStakingApp = await renderApp({ - wagmi: { __customConnectors__: customConnectors }, - }); - - await expect - .element(withAvaxNativeStakingApp.getByText("Stake").first()) - .toBeInTheDocument(); - - await expect - .element( - withAvaxNativeStakingApp - .getByTestId("select-opportunity") - .getByText(avaxNativeStaking.token.symbol) - ) - .toBeInTheDocument(); - - await expect - .element( - withAvaxNativeStakingApp - .getByText(`${APToPercentage(avaxNativeStaking.rewardRate)}%`) - .first() - ) - .toBeInTheDocument(); - - await withAvaxNativeStakingApp.unmount(); - }); - - it("Works correctly with pending action query param without validator address requirement", async ({ - worker, - }) => { - const { customConnectors, setUrl, account, avaxLiquidStaking, requestFn } = - await setup(worker); - - setUrl({ - accountId: account, - yieldId: avaxLiquidStaking.id, - pendingaction: "CLAIM_REWARDS", - }); - - const app = await renderApp({ - wagmi: { __customConnectors__: customConnectors }, - }); - - await expect.element(app.getByText("Claim")).toBeInTheDocument(); - - await expect - .element( - app.getByText("By clicking confirm you agree to", { exact: false }) - ) - .toBeInTheDocument(); - - const confirmButton = app.getByRole("button", { - exact: true, - name: "Confirm", - }); - - await expect.element(confirmButton).toBeInTheDocument(); - - await confirmButton.click(); - - await expect.element(app.getByText("Follow Steps")).toBeInTheDocument(); - - await expect - .poll(() => requestFn, { timeout: 1000 * 5 }) - .toHaveBeenCalledWith({ - method: "eth_sendTransaction", - params: expect.anything(), - }); - - expect(requestFn).toHaveBeenCalledWith({ method: "eth_chainId" }); - - await expect - .element(app.getByText("Successfully claimed rewards", { exact: false })) - .toBeInTheDocument(); - - expect(app.getByText("View Claim rewards transaction")).toBeInTheDocument(); - - app.unmount(); - }); - - it("Works correctly with pending action query param with validator address requirement", async ({ - worker, - }) => { - const { customConnectors, setUrl, account, avaxLiquidStaking, requestFn } = - await setup(worker, { withValidatorAddressesRequired: true }); - - setUrl({ - accountId: account, - yieldId: avaxLiquidStaking.id, - pendingaction: "CLAIM_REWARDS", - }); - - const app = await renderApp({ - wagmi: { __customConnectors__: customConnectors }, - }); - - await expect - .element(app.getByText(avaxLiquidStaking.metadata.name)) - .toBeInTheDocument(); - - await expect - .element(app.getByTestId(avaxLiquidStaking.validators[0].address)) - .toBeInTheDocument(); - - await app.getByTestId(avaxLiquidStaking.validators[0].address).click(); - await app.getByText("Submit").click(); - - await expect.element(app.getByText("Claim")).toBeInTheDocument(); - - await expect - .element( - app.getByText("By clicking confirm you agree to", { exact: false }) - ) - .toBeInTheDocument(); - - await expect - .element(app.getByRole("button", { name: "Confirm" })) - .toBeInTheDocument(); - - await app.getByRole("button", { name: "Confirm" }).click(); - - await expect.element(app.getByText("Follow Steps")).toBeInTheDocument(); - - await expect - .poll(() => requestFn, { timeout: 1000 * 5 }) - .toHaveBeenCalledWith({ - method: "eth_sendTransaction", - params: expect.anything(), - }); - - expect(requestFn).toHaveBeenCalledWith({ method: "eth_chainId" }); - - await expect - .element(app.getByText("Successfully claimed rewards", { exact: false })) - .toBeInTheDocument(); - - await expect - .element(app.getByText("View Claim rewards transaction")) - .toBeInTheDocument(); - - await app.unmount(); - }); - - it("Handles init network correctly", async ({ worker }) => { - const { setUrl, customConnectors, requestFn, getCurrentChainId } = - await setup(worker); - - expect(getCurrentChainId()).not.toBe(1); - setUrl({ network: "ethereum" }); - - const app = await renderApp({ - wagmi: { __customConnectors__: customConnectors }, - }); - - await expect.element(app.getByText("Ethereum")).toBeInTheDocument(); - - await expect - .poll(() => requestFn, { timeout: 1000 * 5 }) - .toHaveBeenCalledWith({ - method: "wallet_switchEthereumChain", - params: [{ chainId: numberToHex(1) }], - }); - - await app.unmount(); - }); -}); diff --git a/packages/widget/tests/use-cases/deep-links-flow/param-validation.dom.test.tsx b/packages/widget/tests/use-cases/deep-links-flow/param-validation.dom.test.tsx new file mode 100644 index 000000000..df08e824d --- /dev/null +++ b/packages/widget/tests/use-cases/deep-links-flow/param-validation.dom.test.tsx @@ -0,0 +1,103 @@ +import { describe, expect, it } from "vitest"; +import { decodeInitParams } from "../../../src/domain/schema/init-params"; +import type { ActionType } from "../../../src/domain/types/action"; +import { setUrl as _setUrl } from "./utils"; + +const decodeCurrentUrl = () => + decodeInitParams({ + externalProviderInitToken: null, + href: window.location.href, + }); + +describe("Deep link param validation", () => { + it("Should validate yieldId param", async () => { + const setAndAssertIsValidYieldIdParam = async ( + yieldId: string, + valid: boolean + ) => { + _setUrl({ yieldId }); + + expect(decodeCurrentUrl().yieldId).toEqual(valid ? yieldId : null); + }; + + await setAndAssertIsValidYieldIdParam("ethereum-eth-native-staking", true); + await setAndAssertIsValidYieldIdParam( + "../ethereum-eth-native-staking", + false + ); + await setAndAssertIsValidYieldIdParam("..", false); + await setAndAssertIsValidYieldIdParam("..%2f", false); + await setAndAssertIsValidYieldIdParam("..%252f", false); + await setAndAssertIsValidYieldIdParam("AAA-%2f..%2f..%2f-whatever", false); + await setAndAssertIsValidYieldIdParam( + "./ethereum-eth-native-staking", + false + ); + await setAndAssertIsValidYieldIdParam( + "ethereum-../eth-native-staking", + false + ); + await setAndAssertIsValidYieldIdParam( + "ethereum-eth-native-staking../", + false + ); + await setAndAssertIsValidYieldIdParam( + "ethereum-eth-native-staking/../", + false + ); + }); + + it("Should validate pendingAction param", async () => { + const setAndAssertIsValidPendingActionParam = async ( + pendingaction: ActionType | (string & {}), + valid: boolean + ) => { + _setUrl({ pendingaction }); + + expect(decodeCurrentUrl().pendingaction).toEqual( + valid ? pendingaction : null + ); + }; + + await setAndAssertIsValidPendingActionParam("CLAIM_REWARDS", true); + await setAndAssertIsValidPendingActionParam("STAKE", true); + await setAndAssertIsValidPendingActionParam("RESTAKE_REWARDS", true); + await setAndAssertIsValidPendingActionParam("../CLAIM_REWARDS", false); + await setAndAssertIsValidPendingActionParam("./CLAIM_REWARDS", false); + await setAndAssertIsValidPendingActionParam("ethereum-../STAKE", false); + await setAndAssertIsValidPendingActionParam("STAKE../", false); + await setAndAssertIsValidPendingActionParam("UNSTAKE/../", false); + }); + + it("Keeps valid params when another param is invalid", () => { + const url = new URL(window.location.href); + url.searchParams.set("network", "not-supported"); + url.searchParams.set("yieldId", "ethereum-eth-native-staking"); + + expect( + decodeInitParams({ + externalProviderInitToken: null, + href: url.href, + }) + ).toMatchObject({ + network: null, + yieldId: "ethereum-eth-native-staking", + }); + }); + + it("Decodes accountId and derives network from the effective token", () => { + const url = new URL(window.location.href); + url.searchParams.set("accountId", encodeURIComponent("js:live:eth:0x123")); + + expect( + decodeInitParams({ + externalProviderInitToken: "ethereum-eth", + href: url.href, + }) + ).toMatchObject({ + accountId: "js:live:eth:0x123", + network: "ethereum", + token: "ethereum-eth", + }); + }); +}); diff --git a/packages/widget/tests/use-cases/deep-links-flow/param-validation.test.tsx b/packages/widget/tests/use-cases/deep-links-flow/param-validation.test.tsx deleted file mode 100644 index 80a576964..000000000 --- a/packages/widget/tests/use-cases/deep-links-flow/param-validation.test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { I18nextProvider } from "react-i18next"; -import type { ActionType } from "../../../src/domain/types/action"; -import { useInitQueryParams } from "../../../src/hooks/use-init-query-params"; -import { SettingsContextProvider } from "../../../src/providers/settings"; -import { i18nInstance } from "../../../src/translation"; -import { describe, expect, it } from "../../utils/test-extend"; -import { renderHook } from "../../utils/test-utils"; -import { setUrl as _setUrl } from "./utils"; - -describe("Deep link param validation", () => { - it("Should validate yieldId param", async () => { - const setAndAssertIsValidYieldIdParam = async ( - yieldId: string, - valid: boolean - ) => { - _setUrl({ yieldId }); - - const result = await renderHook(useInitQueryParams, { - wrapper: ({ children }) => ( - - {children} - - ), - }); - - expect(result.result.current.map((v) => v.yieldId).extract()).toEqual( - valid ? yieldId : null - ); - }; - - await setAndAssertIsValidYieldIdParam("ethereum-eth-native-staking", true); - await setAndAssertIsValidYieldIdParam( - "../ethereum-eth-native-staking", - false - ); - await setAndAssertIsValidYieldIdParam("..", false); - await setAndAssertIsValidYieldIdParam("..%2f", false); - await setAndAssertIsValidYieldIdParam("..%252f", false); - await setAndAssertIsValidYieldIdParam("AAA-%2f..%2f..%2f-whatever", false); - await setAndAssertIsValidYieldIdParam( - "./ethereum-eth-native-staking", - false - ); - await setAndAssertIsValidYieldIdParam( - "ethereum-../eth-native-staking", - false - ); - await setAndAssertIsValidYieldIdParam( - "ethereum-eth-native-staking../", - false - ); - await setAndAssertIsValidYieldIdParam( - "ethereum-eth-native-staking/../", - false - ); - }); - - it("Should validate pendingAction param", async () => { - const setAndAssertIsValidPendingActionParam = async ( - pendingaction: ActionType | (string & {}), - valid: boolean - ) => { - _setUrl({ pendingaction }); - - const { result } = await renderHook(useInitQueryParams, { - wrapper: ({ children }) => ( - - {children} - - ), - }); - - expect(result.current.map((v) => v.pendingaction).extract()).toEqual( - valid ? pendingaction : null - ); - }; - - await setAndAssertIsValidPendingActionParam("CLAIM_REWARDS", true); - await setAndAssertIsValidPendingActionParam("STAKE", true); - await setAndAssertIsValidPendingActionParam("RESTAKE_REWARDS", true); - await setAndAssertIsValidPendingActionParam("../CLAIM_REWARDS", false); - await setAndAssertIsValidPendingActionParam("./CLAIM_REWARDS", false); - await setAndAssertIsValidPendingActionParam("ethereum-../STAKE", false); - await setAndAssertIsValidPendingActionParam("STAKE../", false); - await setAndAssertIsValidPendingActionParam("UNSTAKE/../", false); - }); -}); diff --git a/packages/widget/tests/use-cases/deep-links-flow/setup.ts b/packages/widget/tests/use-cases/deep-links-flow/setup.ts index c551350d1..351fe38f3 100644 --- a/packages/widget/tests/use-cases/deep-links-flow/setup.ts +++ b/packages/widget/tests/use-cases/deep-links-flow/setup.ts @@ -1,20 +1,20 @@ +import { Array as EArray } from "effect"; import { HttpResponse, http } from "msw"; -import { Just } from "purify-ts"; import { vitest } from "vitest"; -import type { YieldCreateManageActionDto } from "../../../src/domain/types/action"; -import { waitForMs } from "../../../src/utils"; +import type { ManageActionCommand } from "../../../src/domain/schema/action-models"; import { legacyYieldFixture, yieldApiActionFixture, yieldApiTransactionFixture, yieldApiValidatorFixture, yieldApiValidatorsFixture, - yieldApiYieldFixture, + yieldApiYieldDtoFixture, } from "../../fixtures"; import { legacyApiRoute, yieldApiRoute } from "../../mocks/api-routes"; import { mockDelay } from "../../mocks/delay"; import { rkMockWallet } from "../../utils/mock-connector"; import type { TestWorker } from "../../utils/test-extend"; +import { waitForMs } from "../../utils/wait"; import { setUrl as _setUrl } from "./utils"; type LegacyTokenDto = ReturnType["token"]; @@ -50,7 +50,7 @@ export const setup = async ( const avaxLiquidStakingRewardRate = 0.0475; const legacyYieldBase = legacyYieldFixture(); - const yieldApiYieldBase = yieldApiYieldFixture(); + const yieldApiYieldBase = yieldApiYieldDtoFixture(); const avaxNativeStaking = legacyYieldFixture({ id: "avalanche-avax-native-staking", token, @@ -122,13 +122,13 @@ export const setup = async ( name: "Benqi", description: "", externalLink: "https://benqi.fi/", - logoURI: "https://assets.stakek.it/providers/benqi.svg", + logoURI: "https://assets.stakek.it/app/composition/providers/benqi.svg", }, rewardTokens: [rewardToken], }, validators: avaxLiquidStakingLegacyValidators, }); - const avaxNativeStakingYieldApi = yieldApiYieldFixture({ + const avaxNativeStakingYieldApi = yieldApiYieldDtoFixture({ id: avaxNativeStaking.id, network: token.network, token, @@ -145,7 +145,7 @@ export const setup = async ( gasFeeToken: token, }, }); - const avaxLiquidStakingYieldApi = yieldApiYieldFixture({ + const avaxLiquidStakingYieldApi = yieldApiYieldDtoFixture({ id: avaxLiquidStaking.id, network: token.network, providerId: avaxLiquidStaking.metadata.provider?.id ?? "benqi", @@ -335,13 +335,10 @@ export const setup = async ( const yieldId = info.params.yieldId as string; const validators = - yieldId === avaxLiquidStaking.id - ? avaxLiquidStakingValidators.length - ? yieldApiValidatorsFixture(avaxLiquidStakingValidators) - : [] - : yieldId === avaxNativeStaking.id - ? [] - : []; + yieldId === avaxLiquidStaking.id && + avaxLiquidStakingValidators.length > 0 + ? yieldApiValidatorsFixture(avaxLiquidStakingValidators) + : []; return HttpResponse.json({ items: validators, @@ -392,7 +389,7 @@ export const setup = async ( } ), http.post(yieldApiRoute("/v1/actions/manage"), async (info) => { - const data = (await info.request.json()) as YieldCreateManageActionDto; + const data = (await info.request.json()) as ManageActionCommand; await mockDelay(); return HttpResponse.json({ @@ -406,8 +403,8 @@ export const setup = async ( amountUsd: pendingAction.amountUsd, transactions: [ yieldApiTransactionFixture({ - id: pendingAction.transactions[0].id, - network: pendingAction.transactions[0].network, + id: EArray.getUnsafe(pendingAction.transactions, 0).id, + network: EArray.getUnsafe(pendingAction.transactions, 0).network, type: "CLAIM_REWARDS", status: "CREATED", unsignedTransaction: @@ -431,7 +428,7 @@ export const setup = async ( return HttpResponse.json({ ...yieldApiTransactionFixture({ type: "CLAIM_REWARDS", - network: pendingAction.transactions[0].network, + network: EArray.getUnsafe(pendingAction.transactions, 0).network, status: "BROADCASTED", id: transactionId, hash: "transaction_hash", @@ -474,9 +471,8 @@ export const setup = async ( case "eth_requestAccounts": return [account]; case "wallet_switchEthereumChain": { - currentChainId = Just(params as { chainId: number }[]) - .map((val) => Number(val[0].chainId)) - .unsafeCoerce(); + const chainParams = params as { chainId: number }[]; + currentChainId = Number(chainParams[0]?.chainId); return currentChainId; } diff --git a/packages/widget/tests/use-cases/external-provider/external-provider.browser.test.tsx b/packages/widget/tests/use-cases/external-provider/external-provider.browser.test.tsx new file mode 100644 index 000000000..51df54502 --- /dev/null +++ b/packages/widget/tests/use-cases/external-provider/external-provider.browser.test.tsx @@ -0,0 +1,169 @@ +import { avalanche, mainnet } from "viem/chains"; +import { userEvent } from "vitest/browser"; +import { SKApp } from "../../../src/App"; +import { solana, ton } from "../../../src/domain/types/chains/misc"; +import type { SKAppProps } from "../../../src/public-api/types"; +import { formatAddress } from "../../../src/shared/lib/general"; +import { describe, expect, it, vi } from "../../utils/test-extend"; +import { renderApp } from "../../utils/test-utils"; +import { setup } from "./setup"; + +describe("External Provider", () => { + it("Handles changing address and supported chains correctly", async ({ + worker, + }) => { + setup(worker); + + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async () => "hash"); + + const skProps = { + apiKey: import.meta.env.VITE_API_KEY, + externalProviders: { + type: "generic", + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + currentAddress: "0xB6c5273e79E2aDD234EBC07d87F3824e0f94B2F7", + supportedChainIds: [mainnet.id, avalanche.id, solana.id, ton.id], + }, + } satisfies SKAppProps; + + const app = await renderApp({ skProps }); + + await expect + .element( + app.getByText(formatAddress(skProps.externalProviders.currentAddress)) + ) + .toBeInTheDocument(); + + const chainNames = { + eth: "Ethereum", + avalanche: "Avalanche", + solana: "Solana", + ton: "Ton", + } as const; + + const chainText = app.getByText( + new RegExp( + `${chainNames.eth}|${chainNames.avalanche}|${chainNames.solana}|${chainNames.ton}` + ) + ); + + await expect.element(chainText).toBeInTheDocument(); + + await chainText.click(); + + const getChainOptions = () => + app + .getByTestId(/^rk-chain-option/) + .elements() + .filter((el) => (el as HTMLButtonElement).type === "button"); + + await expect.poll(() => getChainOptions().length).toBe(4); + + const solanaOption = app + .getByTestId(/^rk-chain-option/) + .getByText(chainNames.solana); + + await solanaOption.click(); + + await expect.poll(() => switchChainSpy).toHaveBeenCalledWith(501); + + await userEvent.keyboard("[Escape]"); + + await app.rerender( + + ); + + await expect + .poll(() => + app.getByText(new RegExp(`${chainNames.avalanche}|${chainNames.ton}`)) + ) + .toBeTruthy(); + + const chainButton = app.getByText( + new RegExp(`${chainNames.avalanche}|${chainNames.ton}`) + ); + + await chainButton.click(); + + await expect.poll(() => getChainOptions().length).toBe(2); + + const tonOption = app + .getByTestId(/^rk-chain-option/) + .getByText(chainNames.ton); + + await tonOption.click(); + + await expect.poll(() => switchChainSpy).toHaveBeenCalledWith(ton.id); + + await userEvent.keyboard("[Escape]"); + + expect(sendTransactionSpy).not.toHaveBeenCalled(); + + skProps.externalProviders.currentAddress = + "0xB7c5273e79E2aDD234EBC07d87F3824e0f94B2f7"; + + await app.rerender( + + ); + + await expect + .element( + app.getByText(formatAddress(skProps.externalProviders.currentAddress)) + ) + .toBeInTheDocument(); + + const prevAddress = skProps.externalProviders.currentAddress; + skProps.externalProviders.currentAddress = ""; + + await app.rerender( + + ); + + await expect + .poll(() => app.getByText(formatAddress(prevAddress)).length) + .toBe(0); + + skProps.externalProviders.currentAddress = + "0xB7c5273e79E2aDD234EBC07d87F3824e0f94B2f7"; + + await app.rerender( + + ); + + await expect + .element( + app.getByText(formatAddress(skProps.externalProviders.currentAddress)) + ) + .toBeInTheDocument(); + app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/external-provider/external-provider.test.tsx b/packages/widget/tests/use-cases/external-provider/external-provider.test.tsx deleted file mode 100644 index c36d6b252..000000000 --- a/packages/widget/tests/use-cases/external-provider/external-provider.test.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { avalanche, mainnet } from "viem/chains"; -import { userEvent } from "vitest/browser"; -import { SKApp, type SKAppProps } from "../../../src/App"; -import { solana, ton } from "../../../src/domain/types/chains/misc"; -import { formatAddress } from "../../../src/utils"; -import { describe, expect, it, vi } from "../../utils/test-extend"; -import { renderApp } from "../../utils/test-utils"; -import { setup } from "./setup"; - -describe("External Provider", () => { - it("Handles changing address and supported chains correctly", async ({ - worker, - }) => { - setup(worker); - - const switchChainSpy = vi.fn(async (_: number) => {}); - const sendTransactionSpy = vi.fn(async () => "hash"); - - const skProps = { - apiKey: import.meta.env.VITE_API_KEY, - externalProviders: { - type: "generic", - provider: { - signMessage: async () => "hash", - switchChain: switchChainSpy, - sendTransaction: sendTransactionSpy, - }, - currentAddress: "0xB6c5273e79E2aDD234EBC07d87F3824e0f94B2F7", - supportedChainIds: [mainnet.id, avalanche.id, solana.id, ton.id], - }, - } satisfies SKAppProps; - - const app = await renderApp({ skProps }); - - await expect - .element( - app.getByText(formatAddress(skProps.externalProviders.currentAddress)) - ) - .toBeInTheDocument(); - - const chainNames = { - eth: "Ethereum", - avalanche: "Avalanche", - solana: "Solana", - ton: "Ton", - } as const; - - const chainText = app.getByText( - new RegExp( - `${chainNames.eth}|${chainNames.avalanche}|${chainNames.solana}|${chainNames.ton}` - ) - ); - - await expect.element(chainText).toBeInTheDocument(); - - await chainText.click(); - - const getChainOptions = () => - app - .getByTestId(/^rk-chain-option/) - .elements() - .filter((el) => (el as HTMLButtonElement).type === "button"); - - await expect.poll(() => getChainOptions().length).toBe(4); - - const solanaOption = app - .getByTestId(/^rk-chain-option/) - .getByText(chainNames.solana); - - await solanaOption.click(); - - await expect.poll(() => switchChainSpy).toHaveBeenCalledWith(501); - - await userEvent.keyboard("[Escape]"); - - await app.rerender( - - ); - - await expect - .poll(() => - app.getByText(new RegExp(`${chainNames.avalanche}|${chainNames.ton}`)) - ) - .toBeTruthy(); - - const chainButton = app.getByText( - new RegExp(`${chainNames.avalanche}|${chainNames.ton}`) - ); - - await chainButton.click(); - - await expect.poll(() => getChainOptions().length).toBe(2); - - const tonOption = app - .getByTestId(/^rk-chain-option/) - .getByText(chainNames.ton); - - await tonOption.click(); - - await expect.poll(() => switchChainSpy).toHaveBeenCalledWith(ton.id); - - await userEvent.keyboard("[Escape]"); - - expect(sendTransactionSpy).not.toHaveBeenCalled(); - - skProps.externalProviders.currentAddress = - "0xB7c5273e79E2aDD234EBC07d87F3824e0f94B2f7"; - - await app.rerender( - - ); - - await expect - .element( - app.getByText(formatAddress(skProps.externalProviders.currentAddress)) - ) - .toBeInTheDocument(); - - const prevAddress = skProps.externalProviders.currentAddress; - skProps.externalProviders.currentAddress = ""; - - await app.rerender( - - ); - - await expect - .poll(() => app.getByText(formatAddress(prevAddress)).length) - .toBe(0); - - skProps.externalProviders.currentAddress = - "0xB7c5273e79E2aDD234EBC07d87F3824e0f94B2f7"; - - await app.rerender( - - ); - - await expect - .element( - app.getByText(formatAddress(skProps.externalProviders.currentAddress)) - ) - .toBeInTheDocument(); - app.unmount(); - }); -}); diff --git a/packages/widget/tests/use-cases/external-provider/setup.ts b/packages/widget/tests/use-cases/external-provider/setup.ts index 36fc10dba..300bb524b 100644 --- a/packages/widget/tests/use-cases/external-provider/setup.ts +++ b/packages/widget/tests/use-cases/external-provider/setup.ts @@ -2,7 +2,7 @@ import { HttpResponse, http } from "msw"; import { legacyYieldFixture, yieldApiValidatorsFixture, - yieldApiYieldFixture, + yieldApiYieldDtoFixture, } from "../../fixtures"; import { legacyApiRoute, yieldApiRoute } from "../../mocks/api-routes"; import { mockDelay } from "../../mocks/delay"; @@ -48,7 +48,7 @@ export const setup = (worker: TestWorker) => { }; const legacyYieldBase = legacyYieldFixture(); - const yieldApiYieldBase = yieldApiYieldFixture(); + const yieldApiYieldBase = yieldApiYieldDtoFixture(); const createLegacyNativeStaking = ({ id, token, @@ -74,7 +74,7 @@ export const setup = (worker: TestWorker) => { id: string; token: LegacyTokenDto; }) => - yieldApiYieldFixture({ + yieldApiYieldDtoFixture({ id, network: token.network, token, diff --git a/packages/widget/tests/use-cases/gas-warning-flow/gas-warning-flow.browser.test.tsx b/packages/widget/tests/use-cases/gas-warning-flow/gas-warning-flow.browser.test.tsx new file mode 100644 index 000000000..6a7c5cd7f --- /dev/null +++ b/packages/widget/tests/use-cases/gas-warning-flow/gas-warning-flow.browser.test.tsx @@ -0,0 +1,226 @@ +import { userEvent } from "vitest/browser"; +import { formatAddress } from "../../../src/shared/lib/general"; +import { describe, expect, it } from "../../utils/test-extend"; +import { renderApp } from "../../utils/test-utils"; +import { setup } from "./setup"; + +describe("Gas warning flow", () => { + const testFn = async ({ + yieldDto, + withWarning, + stakeAmount, + account, + customConnectors, + }: { + stakeAmount: string; + yieldDto: ReturnType< + typeof setup + >["yieldWithSameGasAndStakeToken"]["yieldDto"]; + withWarning: boolean; + } & Pick, "account" | "customConnectors">) => { + const app = await renderApp({ + wagmi: { + __customConnectors__: customConnectors, + forceWalletConnectOnly: false, + }, + }); + + await expect + .element(app.getByText(formatAddress(account))) + .toBeInTheDocument(); + + await app.getByTestId("select-token").click(); + + let selectContainer = app.getByTestId("select-modal__container"); + + await selectContainer + .getByText(yieldDto.token.name, { exact: true }) + .click(); + + await expect + .element( + app + .getByTestId("select-token") + .getByText(yieldDto.token.symbol, { exact: true }) + ) + .toBeInTheDocument(); + + await app.getByTestId("select-opportunity").click(); + + selectContainer = app.getByTestId("select-modal__container"); + + await selectContainer + .getByTestId(new RegExp(`^select-opportunity__item_${yieldDto.id}`)) + .click(); + + await expect + .poll( + () => + app.getByTestId("select-opportunity").getByText(yieldDto.token.symbol) + .length + ) + .greaterThan(0); + + await userEvent.click(app.getByTestId("number-input")); + await userEvent.keyboard(stakeAmount); + + await expect + .element(app.getByRole("button", { name: "Stake", exact: true })) + .toBeInTheDocument(); + await userEvent.click( + app.getByRole("button", { name: "Stake", exact: true }) + ); + + const estimatedGasFee = app.getByTestId("estimated_gas_fee"); + await expect + .poll(() => + estimatedGasFee.element().querySelector(".react-loading-skeleton") + ) + .toBeNull(); + + if (withWarning) { + await expect + .element( + app.getByText("This action is unlikely to succeed", { exact: false }) + ) + .toBeInTheDocument(); + } else { + await expect + .element( + app.getByText("This action is unlikely to succeed", { exact: false }) + ) + .not.toBeInTheDocument(); + } + app.unmount(); + }; + + describe("Stake token same as gas token", () => { + it("Txs gas > gas token amount", async ({ worker }) => { + const { + account, + customConnectors, + yieldWithSameGasAndStakeToken, + setTxGas, + setAvalanceCTokenAmount, + } = setup(worker); + + const txGas = 2; + const totalTxGas = + txGas * yieldWithSameGasAndStakeToken.actionDto.transactions.length; + const gasTokenAmount = totalTxGas - 1; + const stakeAmount = "1"; + + setTxGas({ + yieldId: yieldWithSameGasAndStakeToken.yieldDto.id, + amount: txGas.toString(), + }); + setAvalanceCTokenAmount(gasTokenAmount); + + await testFn({ + stakeAmount, + withWarning: true, + yieldDto: yieldWithSameGasAndStakeToken.yieldDto, + account, + customConnectors, + }); + }); + + it("Txs gas < gas token amount", async ({ worker }) => { + const { + account, + customConnectors, + yieldWithSameGasAndStakeToken, + setTxGas, + setAvalanceCTokenAmount, + } = setup(worker); + + const txGas = 2; + const totalTxGas = + txGas * yieldWithSameGasAndStakeToken.actionDto.transactions.length; + const gasTokenAmount = totalTxGas + 1; + const stakeAmount = "1"; + + setTxGas({ + yieldId: yieldWithSameGasAndStakeToken.yieldDto.id, + amount: txGas.toString(), + }); + setAvalanceCTokenAmount(gasTokenAmount); + + await testFn({ + stakeAmount, + withWarning: false, + yieldDto: yieldWithSameGasAndStakeToken.yieldDto, + account, + customConnectors, + }); + }); + }); + + describe("Stake token different than gas token", () => { + it("Txs gas > gas token amount", async ({ worker }) => { + const { + account, + customConnectors, + yieldWithDifferentGasAndStakeToken, + setTxGas, + setAvalanceCTokenAmount, + setUsdcTokenAmount, + } = setup(worker); + + const txGas = 2; + const totalTxGas = + txGas * + yieldWithDifferentGasAndStakeToken.actionDto.transactions.length; + const gasTokenAmount = totalTxGas - 1; + const stakeAmount = "1"; + + setTxGas({ + yieldId: yieldWithDifferentGasAndStakeToken.yieldDto.id, + amount: txGas.toString(), + }); + setAvalanceCTokenAmount(gasTokenAmount); + setUsdcTokenAmount(Number(stakeAmount)); + + await testFn({ + stakeAmount, + withWarning: true, + yieldDto: yieldWithDifferentGasAndStakeToken.yieldDto, + account, + customConnectors, + }); + }); + + it("Txs gas < gas token amount", async ({ worker }) => { + const { + account, + customConnectors, + yieldWithDifferentGasAndStakeToken, + setTxGas, + setAvalanceCTokenAmount, + setUsdcTokenAmount, + } = setup(worker); + + const txGas = 2; + const totalTxGas = + txGas * + yieldWithDifferentGasAndStakeToken.actionDto.transactions.length; + const gasTokenAmount = totalTxGas + 1; + const stakeAmount = "1"; + + setTxGas({ + yieldId: yieldWithDifferentGasAndStakeToken.yieldDto.id, + amount: txGas.toString(), + }); + setAvalanceCTokenAmount(gasTokenAmount); + setUsdcTokenAmount(Number(stakeAmount)); + + await testFn({ + stakeAmount, + withWarning: false, + yieldDto: yieldWithDifferentGasAndStakeToken.yieldDto, + account, + customConnectors, + }); + }); + }); +}); diff --git a/packages/widget/tests/use-cases/gas-warning-flow/gas-warning-flow.test.tsx b/packages/widget/tests/use-cases/gas-warning-flow/gas-warning-flow.test.tsx deleted file mode 100644 index 06be81071..000000000 --- a/packages/widget/tests/use-cases/gas-warning-flow/gas-warning-flow.test.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { userEvent } from "vitest/browser"; -import { formatAddress } from "../../../src/utils"; -import { describe, expect, it } from "../../utils/test-extend"; -import { renderApp } from "../../utils/test-utils"; -import { setup } from "./setup"; - -describe("Gas warning flow", () => { - const testFn = async ({ - yieldDto, - withWarning, - availableAmount, - account, - customConnectors, - }: { - availableAmount: string; - yieldDto: ReturnType< - typeof setup - >["yieldWithSameGasAndStakeToken"]["yieldDto"]; - withWarning: boolean; - } & Pick, "account" | "customConnectors">) => { - const app = await renderApp({ - wagmi: { - __customConnectors__: customConnectors, - forceWalletConnectOnly: false, - }, - }); - - await expect - .element(app.getByText(formatAddress(account))) - .toBeInTheDocument(); - - await app.getByTestId("select-opportunity").click(); - - const selectContainer = app.getByTestId("select-modal__container"); - - await selectContainer - .getByTestId(new RegExp(`^select-opportunity__item_${yieldDto.id}`)) - .click(); - - await expect - .poll( - () => - app.getByTestId("select-opportunity").getByText(yieldDto.token.symbol) - .length - ) - .greaterThan(0); - - const stakeAmount = availableAmount.toString(); - - await userEvent.click(app.getByTestId("number-input")); - await userEvent.keyboard(stakeAmount); - - await expect - .element(app.getByRole("button", { name: "Stake", exact: true })) - .toBeInTheDocument(); - await userEvent.click( - app.getByRole("button", { name: "Stake", exact: true }) - ); - - if (withWarning) { - await expect - .element( - app.getByText("This action is unlikely to succeed", { exact: false }) - ) - .toBeInTheDocument(); - } else { - await expect - .element( - app.getByText("This action is unlikely to succeed", { exact: false }) - ) - .not.toBeInTheDocument(); - } - app.unmount(); - }; - - describe("Stake token same as gas token", () => { - it("Txs gas > gas token amount", async ({ worker }) => { - const { - account, - customConnectors, - yieldWithSameGasAndStakeToken, - setTxGas, - setAvalanceCTokenAmount, - } = setup(worker); - - const totalTxGas = 4; - const availableAmount = totalTxGas - 1; - - setTxGas({ - yieldId: yieldWithSameGasAndStakeToken.yieldDto.id, - amount: totalTxGas.toString(), - }); - setAvalanceCTokenAmount(availableAmount); - - await testFn({ - availableAmount: availableAmount.toString(), - withWarning: true, - yieldDto: yieldWithSameGasAndStakeToken.yieldDto, - account, - customConnectors, - }); - }); - - it("Txs gas < gas token amount", async ({ worker }) => { - const { - account, - customConnectors, - yieldWithSameGasAndStakeToken, - setTxGas, - setAvalanceCTokenAmount, - } = setup(worker); - - const totalTxGas = 4; - const availableAmount = totalTxGas + 1; - - setTxGas({ - yieldId: yieldWithSameGasAndStakeToken.yieldDto.id, - amount: totalTxGas.toString(), - }); - setAvalanceCTokenAmount(availableAmount); - - await testFn({ - availableAmount: availableAmount.toString(), - withWarning: false, - yieldDto: yieldWithSameGasAndStakeToken.yieldDto, - account, - customConnectors, - }); - }); - }); - - describe("Stake token different than gas token", () => { - it("Txs gas > gas token amount", async ({ worker }) => { - const { - account, - customConnectors, - yieldWithDifferentGasAndStakeToken, - setTxGas, - setUsdcTokenAmount, - } = setup(worker); - - const totalTxGas = 4; - const availableAmount = totalTxGas - 1; - - setTxGas({ - yieldId: yieldWithDifferentGasAndStakeToken.yieldDto.id, - amount: totalTxGas.toString(), - }); - setUsdcTokenAmount(availableAmount); - - await testFn({ - availableAmount: availableAmount.toString(), - withWarning: true, - yieldDto: yieldWithDifferentGasAndStakeToken.yieldDto, - account, - customConnectors, - }); - }); - - it("Txs gas < gas token amount", async ({ worker }) => { - const { - account, - customConnectors, - yieldWithDifferentGasAndStakeToken, - setTxGas, - setUsdcTokenAmount, - } = setup(worker); - - const totalTxGas = 4; - const availableAmount = totalTxGas + 1; - - setTxGas({ - yieldId: yieldWithDifferentGasAndStakeToken.yieldDto.id, - amount: totalTxGas.toString(), - }); - setUsdcTokenAmount(availableAmount); - - await testFn({ - availableAmount: availableAmount.toString(), - withWarning: false, - yieldDto: yieldWithDifferentGasAndStakeToken.yieldDto, - account, - customConnectors, - }); - }); - }); -}); diff --git a/packages/widget/tests/use-cases/gas-warning-flow/setup.ts b/packages/widget/tests/use-cases/gas-warning-flow/setup.ts index 10b33c50f..d2c43e56c 100644 --- a/packages/widget/tests/use-cases/gas-warning-flow/setup.ts +++ b/packages/widget/tests/use-cases/gas-warning-flow/setup.ts @@ -1,18 +1,18 @@ import { HttpResponse, http } from "msw"; import { vitest } from "vitest"; -import type { YieldCreateActionDto } from "../../../src/domain/types/action"; -import { waitForMs } from "../../../src/utils"; +import type { ActionCommand } from "../../../src/domain/schema/action-models"; import { legacyYieldFixture, yieldApiActionFixture, yieldApiTransactionFixture, yieldApiValidatorsFixture, - yieldApiYieldFixture, + yieldApiYieldDtoFixture, } from "../../fixtures"; import { legacyApiRoute, yieldApiRoute } from "../../mocks/api-routes"; import { mockDelay } from "../../mocks/delay"; import { rkMockWallet } from "../../utils/mock-connector"; import type { TestWorker } from "../../utils/test-extend"; +import { waitForMs } from "../../utils/wait"; type LegacyTokenDto = ReturnType["token"]; @@ -36,7 +36,7 @@ export const setup = (worker: TestWorker) => { }; const legacyYieldBase = legacyYieldFixture(); - const yieldApiYieldBase = yieldApiYieldFixture(); + const yieldApiYieldBase = yieldApiYieldDtoFixture(); const createLegacyYield = ({ id, token, @@ -66,7 +66,7 @@ export const setup = (worker: TestWorker) => { token: LegacyTokenDto; gasFeeToken: LegacyTokenDto; }) => - yieldApiYieldFixture({ + yieldApiYieldDtoFixture({ id, network: token.network, token, @@ -275,7 +275,7 @@ export const setup = (worker: TestWorker) => { http.post(yieldApiRoute("/v1/actions/enter"), async (info) => { await mockDelay(); - const body = (await info.request.json()) as YieldCreateActionDto; + const body = (await info.request.json()) as ActionCommand; const selectedYield = body.yieldId === yieldWithSameGasAndStakeToken.yieldDto.id ? yieldWithSameGasAndStakeToken diff --git a/packages/widget/tests/use-cases/geo-block.test.tsx b/packages/widget/tests/use-cases/geo-block.browser.test.tsx similarity index 100% rename from packages/widget/tests/use-cases/geo-block.test.tsx rename to packages/widget/tests/use-cases/geo-block.browser.test.tsx diff --git a/packages/widget/tests/use-cases/renders-initial-page.browser.test.tsx b/packages/widget/tests/use-cases/renders-initial-page.browser.test.tsx new file mode 100644 index 000000000..7e7ee4b05 --- /dev/null +++ b/packages/widget/tests/use-cases/renders-initial-page.browser.test.tsx @@ -0,0 +1,747 @@ +import { HttpResponse, http } from "msw"; +import { userEvent } from "vitest/browser"; +import { DashboardYieldCategory } from "../../src/public-api/types"; +import { legacyYieldFixture, yieldApiYieldDtoFixture } from "../fixtures"; +import { + borrowApiRoute, + legacyApiRoute, + yieldApiRoute, +} from "../mocks/api-routes"; +import { mockDelay } from "../mocks/delay"; +import { rkMockWallet } from "../utils/mock-connector"; +import { describe, expect, it } from "../utils/test-extend"; +import { renderApp } from "../utils/test-utils"; + +type LegacyTokenDto = ReturnType["token"]; + +const emptyBorrowPosition = { + address: "0x0000000000000000000000000000000000000001", + availableToBorrowUsd: "0", + currentLtv: "0", + debtBalances: [], + healthFactor: null, + integrationId: "aave-borrow", + netApy: "0", + netWorthUsd: "0", + network: "ethereum", + supplyBalances: [], + totalBorrowedUsd: "0", + totalCollateralUsd: "0", + totalSuppliedUsd: "0", +} as const; + +const dashboardCategoryYieldsHandler = () => { + const stakeYield = yieldApiYieldDtoFixture({ + id: "ethereum-eth-native-staking", + }); + const defiYield = yieldApiYieldDtoFixture({ + id: "ethereum-usdc-lending", + mechanics: { + ...stakeYield.mechanics, + type: "lending", + }, + }); + const rwaYield = yieldApiYieldDtoFixture({ + id: "ethereum-usdc-real-world-asset", + mechanics: { + ...stakeYield.mechanics, + type: "real_world_asset", + }, + }); + + return http.get(yieldApiRoute("/v1/yields"), async () => { + await mockDelay(); + + const items = [stakeYield, defiYield, rwaYield]; + + return HttpResponse.json({ + items, + total: items.length, + offset: 0, + limit: items.length, + }); + }); +}; + +describe("Renders initial page", () => { + it("Works as expected", async ({ worker }) => { + const avalancheCToken: LegacyTokenDto = { + name: "Avalanche C Chain", + symbol: "AVAX", + decimals: 18, + network: "avalanche-c", + coinGeckoId: "avalanche-2", + logoURI: "https://assets.stakek.it/tokens/avax.svg", + }; + + const ether: LegacyTokenDto = { + network: "ethereum", + name: "Ethereum", + symbol: "ETH", + decimals: 18, + coinGeckoId: "ethereum", + logoURI: "https://assets.stakek.it/tokens/eth.svg", + }; + + const legacyYieldBase = legacyYieldFixture(); + const yieldApiYieldBase = yieldApiYieldDtoFixture(); + const avalancheAvaxNativeStaking = legacyYieldFixture({ + id: "avalanche-avax-native-staking", + token: avalancheCToken, + tokens: [avalancheCToken], + metadata: { + ...legacyYieldBase.metadata, + type: "staking", + gasFeeToken: avalancheCToken, + }, + }); + const etherNativeStaking = legacyYieldFixture({ + id: "ethereum-eth-etherfi-staking", + token: ether, + tokens: [ether], + metadata: { + ...legacyYieldBase.metadata, + type: "staking", + gasFeeToken: ether, + }, + }); + + const avalancheAvaxNativeStakingYieldApi = yieldApiYieldDtoFixture({ + id: avalancheAvaxNativeStaking.id, + network: avalancheCToken.network, + token: avalancheCToken, + tokens: [avalancheCToken], + inputTokens: [avalancheCToken], + outputToken: avalancheCToken, + mechanics: { + ...yieldApiYieldBase.mechanics, + type: "staking", + gasFeeToken: avalancheCToken, + }, + }); + const etherNativeStakingYieldApi = yieldApiYieldDtoFixture({ + id: etherNativeStaking.id, + network: ether.network, + token: ether, + tokens: [ether], + inputTokens: [ether], + outputToken: ether, + mechanics: { + ...yieldApiYieldBase.mechanics, + type: "staking", + gasFeeToken: ether, + }, + }); + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([ + etherNativeStaking.token.network, + avalancheAvaxNativeStaking.token.network, + ]); + }), + + http.get(legacyApiRoute("/v1/tokens"), async () => { + await mockDelay(); + + return HttpResponse.json([ + { token: ether, availableYields: [etherNativeStaking.id] }, + { + token: avalancheCToken, + availableYields: [avalancheAvaxNativeStaking.id], + }, + ]); + }), + + http.get( + legacyApiRoute(`/v1/yields/${etherNativeStaking.id}`), + async () => { + await mockDelay(); + + return HttpResponse.json(etherNativeStaking); + } + ), + http.get(yieldApiRoute("/v1/yields"), async () => { + await mockDelay(); + + const items = [ + etherNativeStakingYieldApi, + avalancheAvaxNativeStakingYieldApi, + ]; + + return HttpResponse.json({ + items, + total: items.length, + offset: 0, + limit: items.length, + }); + }), + http.get( + yieldApiRoute(`/v1/yields/${etherNativeStaking.id}`), + async () => { + await mockDelay(); + + return HttpResponse.json(etherNativeStakingYieldApi); + } + ), + http.get( + legacyApiRoute(`/v1/yields/${avalancheAvaxNativeStaking.id}`), + async () => { + await mockDelay(); + + return HttpResponse.json(avalancheAvaxNativeStaking); + } + ), + http.get( + yieldApiRoute(`/v1/yields/${avalancheAvaxNativeStaking.id}`), + async () => { + await mockDelay(); + + return HttpResponse.json(avalancheAvaxNativeStakingYieldApi); + } + ) + ); + + const app = await renderApp(); + + await expect.element(app.getByTestId("number-input")).toBeInTheDocument(); + await expect.element(app.getByText("Manage")).toBeInTheDocument(); + await expect.element(app.getByText("Connect Wallet")).toBeInTheDocument(); + + app.unmount(); + }); + + it("uses category dashboard yield grouping by default with Borrow disabled", async ({ + worker, + }) => { + worker.use(dashboardCategoryYieldsHandler()); + + const app = await renderApp({ + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + dashboardVariant: true, + }, + }); + + await expect.element(app.getByText("Stake")).toBeInTheDocument(); + await expect.element(app.getByText("DeFi")).toBeInTheDocument(); + await expect.element(app.getByText("RWA")).toBeInTheDocument(); + await expect.element(app.getByText("Manage")).toBeInTheDocument(); + await expect.element(app.getByText("Activity")).toBeInTheDocument(); + + const tabsSection = app.container.querySelector("[data-rk='tabs-section']"); + const tabsText = tabsSection?.textContent ?? ""; + + expect(tabsText).toContain("Stake"); + expect(tabsText).toContain("DeFi"); + expect(tabsText).toContain("RWA"); + expect(tabsText).not.toContain("Borrow"); + expect(tabsText).not.toContain("Earn"); + + app.unmount(); + }); + + it("shows Borrow when enabled with dashboard category grouping", async ({ + worker, + }) => { + worker.use(dashboardCategoryYieldsHandler()); + + const app = await renderApp({ + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + + await expect.element(app.getByText("Stake")).toBeInTheDocument(); + await expect.element(app.getByText("DeFi")).toBeInTheDocument(); + await expect.element(app.getByText("RWA")).toBeInTheDocument(); + await expect.element(app.getByText("Borrow")).toBeInTheDocument(); + + const tabsSection = app.container.querySelector("[data-rk='tabs-section']"); + const tabsText = tabsSection?.textContent ?? ""; + + expect(tabsText).toContain("Stake"); + expect(tabsText).toContain("DeFi"); + expect(tabsText).toContain("RWA"); + expect(tabsText).toContain("Borrow"); + expect(tabsText).toContain("Manage"); + expect(tabsText).toContain("Activity"); + + app.unmount(); + }); + + it("hides Borrow when dashboard yield grouping is flat", async () => { + const app = await renderApp({ + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + yieldGrouping: "flat", + }, + }); + + const tabsSection = app.container.querySelector("[data-rk='tabs-section']"); + const tabsText = tabsSection?.textContent ?? ""; + + expect(tabsText).toContain("Earn"); + expect(tabsText).not.toContain("Borrow"); + expect(tabsText).toContain("Manage"); + expect(tabsText).toContain("Activity"); + + app.unmount(); + }); + + it("uses the configured dashboard category tab order", async ({ worker }) => { + worker.use(dashboardCategoryYieldsHandler()); + + const app = await renderApp({ + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + dashboardYieldCategoryOrder: [ + DashboardYieldCategory.Stake, + DashboardYieldCategory.DeFi, + DashboardYieldCategory.RWA, + ], + }, + }); + + await expect + .element(app.getByText("Stake", { exact: true })) + .toBeInTheDocument(); + await expect + .element(app.getByText("DeFi", { exact: true })) + .toBeInTheDocument(); + await expect + .element(app.getByText("RWA", { exact: true })) + .toBeInTheDocument(); + await expect + .element(app.getByText("Borrow", { exact: true })) + .toBeInTheDocument(); + await expect + .element(app.getByText("Manage", { exact: true })) + .toBeInTheDocument(); + await expect + .element(app.getByText("Activity", { exact: true })) + .toBeInTheDocument(); + + const tabsSection = app.container.querySelector("[data-rk='tabs-section']"); + const tabsText = tabsSection?.textContent ?? ""; + + expect(tabsText.indexOf("Stake")).toBeLessThan(tabsText.indexOf("DeFi")); + expect(tabsText.indexOf("DeFi")).toBeLessThan(tabsText.indexOf("RWA")); + expect(tabsText.indexOf("RWA")).toBeLessThan(tabsText.indexOf("Borrow")); + expect(tabsText.indexOf("Borrow")).toBeLessThan(tabsText.indexOf("Manage")); + expect(tabsText.indexOf("Manage")).toBeLessThan( + tabsText.indexOf("Activity") + ); + + app.unmount(); + }); + + it("updates the selected dashboard category after route tab changes", async ({ + worker, + }) => { + worker.use(dashboardCategoryYieldsHandler()); + + const app = await renderApp({ + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + const clickTab = (label: string) => { + const tabsSection = app.container.querySelector( + "[data-rk='tabs-section']" + ); + const tab = [...(tabsSection?.querySelectorAll("p") ?? [])].find( + (el) => el.textContent === label + ); + + expect(tab).not.toBeUndefined(); + tab?.click(); + }; + + await expect + .element(app.getByText("RWA")) + .toHaveAttribute("data-state", "selected"); + + clickTab("DeFi"); + + await expect + .element(app.getByText("DeFi")) + .toHaveAttribute("data-state", "selected"); + await expect + .element(app.getByText("RWA")) + .toHaveAttribute("data-state", "default"); + + clickTab("Borrow"); + await expect + .element(app.getByText("Borrow").first()) + .toHaveAttribute("data-state", "selected"); + + clickTab("Stake"); + await expect + .element(app.getByText("Stake").first()) + .toHaveAttribute("data-state", "selected"); + + clickTab("Manage"); + await expect + .element(app.getByText("Manage").first()) + .toHaveAttribute("data-state", "selected"); + + clickTab("RWA"); + await expect + .element(app.getByText("RWA").first()) + .toHaveAttribute("data-state", "selected"); + + clickTab("Activity"); + await expect + .element(app.getByText("Activity").first()) + .toHaveAttribute("data-state", "selected"); + + clickTab("DeFi"); + await expect + .element(app.getByText("DeFi").first()) + .toHaveAttribute("data-state", "selected"); + + app.unmount(); + }); + + it("opens the native Borrow dashboard tab", async ({ worker }) => { + const tokenBalanceRequestSignals: AbortSignal[] = []; + const yieldBalanceRequestSignals: AbortSignal[] = []; + + worker.use( + http.post( + legacyApiRoute("/v1/tokens/balances/scan"), + async ({ request }) => { + tokenBalanceRequestSignals.push(request.signal); + await mockDelay(); + return HttpResponse.json([]); + } + ), + http.post(yieldApiRoute("/v1/yields/balances"), async ({ request }) => { + yieldBalanceRequestSignals.push(request.signal); + await mockDelay(); + return HttpResponse.json({ items: [], errors: [] }); + }), + http.get(borrowApiRoute("/v1/positions"), () => + HttpResponse.json(emptyBorrowPosition) + ), + http.get(borrowApiRoute("/v1/integrations"), () => + HttpResponse.json([ + { + id: "aave-borrow", + providerId: "aave", + name: "Aave V3", + networks: ["ethereum"], + metadata: { + description: "Aave lending and borrowing", + externalLink: "https://aave.com", + logoURI: "https://assets.stakek.it/protocols/aave.svg", + }, + actions: [], + }, + ]) + ), + http.get(borrowApiRoute("/v1/markets"), () => + HttpResponse.json({ + total: 1, + offset: 0, + limit: 100, + items: [ + { + id: "aave-v3-ethereum-usdc", + integrationId: "aave-borrow", + network: "ethereum", + type: "pool", + poolAddress: "0x0000000000000000000000000000000000000001", + loanToken: { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + collateralTokens: [ + { + token: { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + priceUsd: "2000", + maxLtv: "0.8", + liquidationThreshold: "0.85", + liquidationPenalty: "0.05", + supplyRate: "0.02", + }, + { + token: { + address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + symbol: "WBTC", + name: "Wrapped Bitcoin", + decimals: 8, + }, + priceUsd: "60000", + maxLtv: "0.75", + liquidationThreshold: "0.8", + liquidationPenalty: "0.05", + supplyRate: "0.015", + }, + ], + borrowRate: "0.06", + totalSupply: "1000000", + totalSupplyRaw: "1000000000000", + totalBorrow: "500000", + totalBorrowRaw: "500000000000", + availableLiquidity: "500000", + availableLiquidityRaw: "500000000000", + utilizationRate: "0.5", + loanTokenPriceUsd: "1", + isBorrowEnabled: true, + supplyCollateralFeeBps: "0", + feeWrapperAddress: null, + minLoan: null, + }, + { + id: "aave-v3-ethereum-dai", + integrationId: "aave-borrow", + network: "ethereum", + type: "pool", + poolAddress: "0x0000000000000000000000000000000000000002", + loanToken: { + address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + }, + collateralTokens: [ + { + token: { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + priceUsd: "2000", + maxLtv: "0.8", + liquidationThreshold: "0.85", + liquidationPenalty: "0.05", + supplyRate: "0.02", + }, + ], + borrowRate: "0.04", + totalSupply: "500000", + totalSupplyRaw: "500000000000000000000000", + totalBorrow: "125000", + totalBorrowRaw: "125000000000000000000000", + availableLiquidity: "375000", + availableLiquidityRaw: "375000000000000000000000", + utilizationRate: "0.25", + loanTokenPriceUsd: "1", + isBorrowEnabled: true, + supplyCollateralFeeBps: "0", + feeWrapperAddress: null, + minLoan: null, + }, + ], + }) + ) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: rkMockWallet({ + accounts: ["0x0000000000000000000000000000000000000001"], + }), + }, + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + + await userEvent.click(app.getByText("Borrow")); + + await expect.element(app.getByText("Borrow APY")).toBeInTheDocument(); + await expect.element(app.getByText("Market stats")).toBeInTheDocument(); + await expect.element(app.getByText("LTV ratio")).toBeInTheDocument(); + await expect + .poll(() => tokenBalanceRequestSignals.length) + .toBeGreaterThanOrEqual(1); + await expect + .poll(() => yieldBalanceRequestSignals.length) + .toBeGreaterThanOrEqual(1); + + await app.getByTestId("borrow-collateral-select").click(); + await expect + .element(app.getByText("Select collateral")) + .toBeInTheDocument(); + await app + .getByTestId("select-modal__container") + .getByTestId( + "borrow-collateral-select__item_0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" + ) + .click(); + await expect.element(app.getByText("WBTC / USDC")).toBeInTheDocument(); + + await app.getByTestId("borrow-market-select").click(); + await expect + .element(app.getByText("Select borrow market")) + .toBeInTheDocument(); + await app + .getByTestId("select-modal__container") + .getByTestId("borrow-market-select__group_dai") + .click(); + await app + .getByTestId("select-modal__container") + .getByTestId("borrow-market-select__item_aave-v3-ethereum-dai") + .click(); + await expect.element(app.getByText("WETH / DAI")).toBeInTheDocument(); + + await userEvent.click(app.getByTestId("number-input").last()); + await userEvent.keyboard("1"); + await expect + .element(app.getByText("Amount exceeds wallet balance.")) + .toBeInTheDocument(); + expect(app.container.textContent).not.toContain("3M"); + expect(tokenBalanceRequestSignals.every((signal) => !signal.aborted)).toBe( + true + ); + expect(yieldBalanceRequestSignals.every((signal) => !signal.aborted)).toBe( + true + ); + + app.unmount(); + }); + + it("opens the native Borrow review screen", async ({ worker }) => { + const account = "0x0000000000000000000000000000000000000001"; + + worker.use( + http.post(legacyApiRoute("/v1/tokens/balances/scan"), () => + HttpResponse.json([ + { + token: { + network: "ethereum", + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + amount: "1", + availableYields: [], + }, + ]) + ), + http.get(borrowApiRoute("/v1/positions"), () => + HttpResponse.json(emptyBorrowPosition) + ), + http.get(borrowApiRoute("/v1/integrations"), () => + HttpResponse.json([ + { + id: "aave-borrow", + providerId: "aave", + name: "Aave V3", + networks: ["ethereum"], + metadata: { + description: "Aave lending and borrowing", + externalLink: "https://aave.com", + logoURI: "https://assets.stakek.it/protocols/aave.svg", + }, + actions: [], + }, + ]) + ), + http.get(borrowApiRoute("/v1/markets"), () => + HttpResponse.json({ + total: 1, + offset: 0, + limit: 100, + items: [ + { + id: "aave-v3-ethereum-usdc", + integrationId: "aave-borrow", + network: "ethereum", + type: "pool", + poolAddress: "0x0000000000000000000000000000000000000001", + loanToken: { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + collateralTokens: [ + { + token: { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + priceUsd: "2000", + maxLtv: "0.8", + liquidationThreshold: "0.85", + liquidationPenalty: "0.05", + supplyRate: "0.02", + }, + ], + borrowRate: "0.06", + totalSupply: "1000000", + totalSupplyRaw: "1000000000000", + totalBorrow: "500000", + totalBorrowRaw: "500000000000", + availableLiquidity: "500000", + availableLiquidityRaw: "500000000000", + utilizationRate: "0.5", + loanTokenPriceUsd: "1", + isBorrowEnabled: true, + supplyCollateralFeeBps: "0", + feeWrapperAddress: null, + minLoan: null, + }, + ], + }) + ) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: rkMockWallet({ accounts: [account] }), + }, + skProps: { + apiKey: import.meta.env.VITE_API_KEY, + borrowEnabled: true, + dashboardVariant: true, + }, + }); + + await userEvent.click(app.getByText("Borrow")); + await expect.element(app.getByText("Borrow APY")).toBeInTheDocument(); + + await userEvent.click(app.getByTestId("number-input").first()); + await userEvent.keyboard("25"); + await userEvent.click(app.getByTestId("number-input").last()); + await userEvent.keyboard("0.5"); + await app.getByRole("button", { name: "Review borrow" }).click(); + + await expect + .element(app.getByText("Borrow and supply collateral")) + .toBeInTheDocument(); + await expect.element(app.getByText("25 USDC")).toBeInTheDocument(); + await expect.element(app.getByText("0.5 WETH")).toBeInTheDocument(); + await expect + .element(app.getByText("aave-v3-ethereum-usdc")) + .toBeInTheDocument(); + + app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/renders-initial-page.test.tsx b/packages/widget/tests/use-cases/renders-initial-page.test.tsx deleted file mode 100644 index 369194f51..000000000 --- a/packages/widget/tests/use-cases/renders-initial-page.test.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { HttpResponse, http } from "msw"; -import { DashboardYieldCategory } from "../../src/domain/types/yields"; -import { legacyYieldFixture, yieldApiYieldFixture } from "../fixtures"; -import { legacyApiRoute, yieldApiRoute } from "../mocks/api-routes"; -import { mockDelay } from "../mocks/delay"; -import { describe, expect, it } from "../utils/test-extend"; -import { renderApp } from "../utils/test-utils"; - -type LegacyTokenDto = ReturnType["token"]; - -describe("Renders initial page", () => { - it("Works as expected", async ({ worker }) => { - const avalancheCToken: LegacyTokenDto = { - name: "Avalanche C Chain", - symbol: "AVAX", - decimals: 18, - network: "avalanche-c", - coinGeckoId: "avalanche-2", - logoURI: "https://assets.stakek.it/tokens/avax.svg", - }; - - const ether: LegacyTokenDto = { - network: "ethereum", - name: "Ethereum", - symbol: "ETH", - decimals: 18, - coinGeckoId: "ethereum", - logoURI: "https://assets.stakek.it/tokens/eth.svg", - }; - - const legacyYieldBase = legacyYieldFixture(); - const yieldApiYieldBase = yieldApiYieldFixture(); - const avalancheAvaxNativeStaking = legacyYieldFixture({ - id: "avalanche-avax-native-staking", - token: avalancheCToken, - tokens: [avalancheCToken], - metadata: { - ...legacyYieldBase.metadata, - type: "staking", - gasFeeToken: avalancheCToken, - }, - }); - const etherNativeStaking = legacyYieldFixture({ - id: "ethereum-eth-etherfi-staking", - token: ether, - tokens: [ether], - metadata: { - ...legacyYieldBase.metadata, - type: "staking", - gasFeeToken: ether, - }, - }); - - const avalancheAvaxNativeStakingYieldApi = yieldApiYieldFixture({ - id: avalancheAvaxNativeStaking.id, - network: avalancheCToken.network, - token: avalancheCToken, - tokens: [avalancheCToken], - inputTokens: [avalancheCToken], - outputToken: avalancheCToken, - mechanics: { - ...yieldApiYieldBase.mechanics, - type: "staking", - gasFeeToken: avalancheCToken, - }, - }); - const etherNativeStakingYieldApi = yieldApiYieldFixture({ - id: etherNativeStaking.id, - network: ether.network, - token: ether, - tokens: [ether], - inputTokens: [ether], - outputToken: ether, - mechanics: { - ...yieldApiYieldBase.mechanics, - type: "staking", - gasFeeToken: ether, - }, - }); - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - return HttpResponse.json([ - etherNativeStaking.token.network, - avalancheAvaxNativeStaking.token.network, - ]); - }), - - http.get(legacyApiRoute("/v1/tokens"), async () => { - await mockDelay(); - - return HttpResponse.json([ - { token: ether, availableYields: [etherNativeStaking.id] }, - { - token: avalancheCToken, - availableYields: [avalancheAvaxNativeStaking.id], - }, - ]); - }), - - http.get( - legacyApiRoute(`/v1/yields/${etherNativeStaking.id}`), - async () => { - await mockDelay(); - - return HttpResponse.json(etherNativeStaking); - } - ), - http.get(yieldApiRoute("/v1/yields"), async () => { - await mockDelay(); - - const items = [ - etherNativeStakingYieldApi, - avalancheAvaxNativeStakingYieldApi, - ]; - - return HttpResponse.json({ - items, - total: items.length, - offset: 0, - limit: items.length, - }); - }), - http.get( - yieldApiRoute(`/v1/yields/${etherNativeStaking.id}`), - async () => { - await mockDelay(); - - return HttpResponse.json(etherNativeStakingYieldApi); - } - ), - http.get( - legacyApiRoute(`/v1/yields/${avalancheAvaxNativeStaking.id}`), - async () => { - await mockDelay(); - - return HttpResponse.json(avalancheAvaxNativeStaking); - } - ), - http.get( - yieldApiRoute(`/v1/yields/${avalancheAvaxNativeStaking.id}`), - async () => { - await mockDelay(); - - return HttpResponse.json(avalancheAvaxNativeStakingYieldApi); - } - ) - ); - - const app = await renderApp(); - - await expect.element(app.getByTestId("number-input")).toBeInTheDocument(); - await expect.element(app.getByText("Manage")).toBeInTheDocument(); - await expect.element(app.getByText("Connect Wallet")).toBeInTheDocument(); - - app.unmount(); - }); - - it("uses category dashboard yield grouping by default", async () => { - const app = await renderApp({ - skProps: { - apiKey: import.meta.env.VITE_API_KEY, - dashboardVariant: true, - }, - }); - - await expect.element(app.getByText("Stake")).toBeInTheDocument(); - await expect.element(app.getByText("DeFi")).toBeInTheDocument(); - await expect.element(app.getByText("RWA")).toBeInTheDocument(); - await expect.element(app.getByText("Manage")).toBeInTheDocument(); - await expect.element(app.getByText("Activity")).toBeInTheDocument(); - - const tabsSection = app.container.querySelector("[data-rk='tabs-section']"); - const tabsText = tabsSection?.textContent ?? ""; - - expect(tabsText).toContain("Stake"); - expect(tabsText).toContain("DeFi"); - expect(tabsText).toContain("RWA"); - expect(tabsText).not.toContain("Earn"); - - app.unmount(); - }); - - it("uses the configured dashboard category tab order", async () => { - const app = await renderApp({ - skProps: { - apiKey: import.meta.env.VITE_API_KEY, - dashboardVariant: true, - dashboardYieldCategoryOrder: [ - DashboardYieldCategory.Stake, - DashboardYieldCategory.DeFi, - DashboardYieldCategory.RWA, - ], - }, - }); - - await expect.element(app.getByText("Stake")).toBeInTheDocument(); - await expect.element(app.getByText("DeFi")).toBeInTheDocument(); - await expect.element(app.getByText("RWA")).toBeInTheDocument(); - await expect.element(app.getByText("Manage")).toBeInTheDocument(); - await expect.element(app.getByText("Activity")).toBeInTheDocument(); - - const tabsSection = app.container.querySelector("[data-rk='tabs-section']"); - const tabsText = tabsSection?.textContent ?? ""; - - expect(tabsText.indexOf("Stake")).toBeLessThan(tabsText.indexOf("DeFi")); - expect(tabsText.indexOf("DeFi")).toBeLessThan(tabsText.indexOf("RWA")); - expect(tabsText.indexOf("RWA")).toBeLessThan(tabsText.indexOf("Manage")); - expect(tabsText.indexOf("Manage")).toBeLessThan( - tabsText.indexOf("Activity") - ); - - app.unmount(); - }); -}); diff --git a/packages/widget/tests/use-cases/rwa-kyc-flow.browser.test.tsx b/packages/widget/tests/use-cases/rwa-kyc-flow.browser.test.tsx new file mode 100644 index 000000000..6bcc2381f --- /dev/null +++ b/packages/widget/tests/use-cases/rwa-kyc-flow.browser.test.tsx @@ -0,0 +1,524 @@ +import { HttpResponse, http } from "msw"; +import { mainnet } from "viem/chains"; +import { userEvent } from "vitest/browser"; +import { createConfig, createConnector, http as wagmiHttp } from "wagmi"; +import { connect, reconnect } from "wagmi/actions"; +import { KycGateCard } from "../../src/features/earn/ui/components/kyc-gate-card"; +import type { SKAppProps } from "../../src/public-api/types"; +import { formatAddress } from "../../src/shared/lib/general"; +import { yieldApiYieldDtoFixture } from "../fixtures"; +import { legacyApiRoute, yieldApiRoute } from "../mocks/api-routes"; +import { mockDelay } from "../mocks/delay"; +import { describe, expect, it, vi } from "../utils/test-extend"; +import { render, renderApp } from "../utils/test-utils"; +import { setup as setupStakingFlow } from "./staking-flow/setup"; +import { setup as setupTrustPosition } from "./trust-incentive-apy/setup"; + +const account = "0xB6c5273e79E2aDD234EBC07d87F3824e0f94B2F7"; + +const makeTestConnector = ({ + id, + onConnect = async () => {}, +}: { + readonly id: string; + readonly onConnect?: () => Promise; +}) => + createConnector((config) => ({ + id, + name: id, + type: id, + connect: async (parameters) => { + await onConnect(); + + return { + accounts: parameters?.withCapabilities + ? [{ address: account, capabilities: {} }] + : [account], + chainId: mainnet.id, + } as never; + }, + disconnect: async () => {}, + getAccounts: async () => [account], + getChainId: async () => mainnet.id, + getProvider: async () => ({}), + isAuthorized: async () => true, + onAccountsChanged: () => {}, + onChainChanged: () => {}, + onDisconnect: () => config.emitter.emit("disconnect"), + })); + +const makeBlockingReconnect = () => { + const started = Promise.withResolvers(); + const release = Promise.withResolvers(); + const connector = makeTestConnector({ + id: "blocking-connector", + onConnect: async () => { + started.resolve(); + await release.promise; + }, + }); + const wagmiConfig = createConfig({ + chains: [mainnet], + connectors: [connector], + storage: null, + transports: { [mainnet.id]: wagmiHttp() }, + }); + + return { + release: release.resolve, + run: () => reconnect(wagmiConfig), + started: started.promise, + }; +}; + +const persistStaleWagmiConnection = async () => { + const wagmiConfig = createConfig({ + chains: [mainnet], + connectors: [makeTestConnector({ id: "persisted-non-external-connector" })], + transports: { [mainnet.id]: wagmiHttp() }, + }); + const connector = wagmiConfig.connectors[0]; + + if (!connector) throw new Error("Expected the test connector"); + + await connect(wagmiConfig, { connector }); + await vi.waitFor(() => { + expect(window.localStorage.getItem("wagmi.store")).not.toBeNull(); + }); +}; + +const skProps = { + apiKey: import.meta.env.VITE_API_KEY, + externalProviders: { + type: "generic", + provider: { + signMessage: async () => "signature", + switchChain: async () => {}, + sendTransaction: async () => "hash", + }, + currentAddress: account, + currentChain: mainnet.id, + supportedChainIds: [mainnet.id], + }, +} satisfies SKAppProps; + +const mockKycStatus = ({ status }: { status: string }) => { + let calls = 0; + let currentStatus = status; + let requestedAddress: string | null = null; + + const handler = http.get( + yieldApiRoute("/v1/yields/:yieldId/kyc/status"), + async ({ request }) => { + await mockDelay(); + calls += 1; + requestedAddress = new URL(request.url).searchParams.get("address"); + + return HttpResponse.json({ + kycStatus: currentStatus, + kycUrl: "https://issuer.example/verify", + }); + } + ); + + return { + handler, + getCalls: () => calls, + getRequestedAddress: () => requestedAddress, + setStatus: (newStatus: string) => { + currentStatus = newStatus; + }, + }; +}; + +const mockKycRequiredDefaultYield = () => { + const baseYield = yieldApiYieldDtoFixture(); + const kycRequiredYield = yieldApiYieldDtoFixture({ + ...baseYield, + mechanics: { + ...baseYield.mechanics, + requirements: { + kycRequired: true, + kyc: { + kycMode: "oauth_redirect", + iframeAllowed: false, + authorizeUrl: "https://issuer.example/verify", + eligibility: { + defaultPolicy: "allow", + countries: [], + blockedCountries: [], + blockedSubdivisions: [], + usPersonAllowed: true, + investorEligibility: [], + subjectTypes: ["KYC"], + }, + }, + }, + }, + }); + + return [ + http.post(legacyApiRoute("/v1/tokens/balances/scan"), async () => { + await mockDelay(); + + return HttpResponse.json([ + { + token: baseYield.token, + amount: "1", + availableYields: [baseYield.id], + }, + ]); + }), + http.post(legacyApiRoute("/v1/tokens/balances"), async () => { + await mockDelay(); + + return HttpResponse.json([ + { + token: baseYield.token, + amount: "1", + availableYields: [baseYield.id], + }, + ]); + }), + http.get(yieldApiRoute("/v1/yields"), async () => { + await mockDelay(); + + return HttpResponse.json({ + items: [kycRequiredYield], + total: 1, + offset: 0, + limit: 1, + }); + }), + http.get(yieldApiRoute(`/v1/yields/${baseYield.id}`), async () => { + await mockDelay(); + + return HttpResponse.json(kycRequiredYield); + }), + ]; +}; + +describe("RWA KYC flow", () => { + it("ignores a persisted non-external connection while another reconnect is running", async ({ + worker, + }) => { + const reconnectBlocker = makeBlockingReconnect(); + window.localStorage.removeItem("wagmi.store"); + await persistStaleWagmiConnection(); + const blockingReconnect = reconnectBlocker.run(); + await reconnectBlocker.started; + + try { + const kycStatus = mockKycStatus({ status: "not_started" }); + + worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); + + const app = await renderApp({ skProps }); + + try { + await expect + .element(app.getByTestId("kyc-gate-card-start_kyc"), { + timeout: 5_000, + }) + .toBeInTheDocument(); + await expect.poll(kycStatus.getRequestedAddress).toBe(account); + } finally { + await app.unmount(); + } + } finally { + reconnectBlocker.release(); + await blockingReconnect; + window.localStorage.removeItem("wagmi.store"); + } + }); + + it("shows not started verification card and fetches wallet status", async ({ + worker, + }) => { + const kycStatus = mockKycStatus({ status: "not_started" }); + + worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); + + const app = await renderApp({ skProps }); + + await expect + .element(app.getByTestId("kyc-gate-card-start_kyc")) + .toBeInTheDocument(); + await expect + .element(app.getByText("Identity verification required")) + .toBeInTheDocument(); + await expect.poll(kycStatus.getRequestedAddress).toBe(account); + + await app.unmount(); + }); + + it("opens iframe modal for iframe-allowed verification and refreshes status on close", async () => { + const onCheckStatus = vi.fn(); + const app = await render( + + ); + + await expect + .element(app.getByTestId("kyc-gate-card-start_kyc")) + .toBeInTheDocument(); + + await userEvent.click(app.getByTestId("kyc-gate-verify")); + + await expect + .element(app.getByTestId("kyc-verification-modal")) + .toBeInTheDocument(); + await expect + .element(app.getByTestId("kyc-verification-iframe")) + .toHaveAttribute("src", "https://issuer.example/verify"); + + await userEvent.click(app.getByTestId("kyc-verification-close")); + + expect(onCheckStatus).toHaveBeenCalledOnce(); + await expect + .poll(() => app.getByTestId(/^kyc-gate-card/).elements().length) + .toBe(1); + + await app.unmount(); + }); + + it("opens verification externally when iframe is not allowed", async ({ + worker, + }) => { + const openSpy = vi.spyOn(window, "open").mockImplementation(() => null); + const kycStatus = mockKycStatus({ status: "not_started" }); + + worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); + + const app = await renderApp({ skProps }); + + await expect + .element(app.getByTestId("kyc-gate-card-start_kyc")) + .toBeInTheDocument(); + + await userEvent.click(app.getByTestId("kyc-gate-verify")); + + expect(openSpy).toHaveBeenCalledWith( + "https://issuer.example/verify", + "_blank", + "noopener,noreferrer" + ); + await expect + .poll(() => app.getByTestId("kyc-verification-modal").elements().length) + .toBe(0); + + openSpy.mockRestore(); + await app.unmount(); + }); + + it("refreshes pending status with Check status", async ({ worker }) => { + const kycStatus = mockKycStatus({ status: "pending" }); + + worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); + + const app = await renderApp({ skProps }); + + await expect + .element(app.getByTestId("kyc-gate-card-pending")) + .toBeInTheDocument(); + + kycStatus.setStatus("approved"); + await userEvent.click(app.getByTestId("kyc-gate-check-status")); + + await expect + .poll(() => app.getByTestId(/^kyc-gate-card/).elements().length) + .toBe(0); + + await app.unmount(); + }); + + it("shows rejected and unknown states", async ({ worker }) => { + worker.use( + ...mockKycRequiredDefaultYield(), + mockKycStatus({ status: "rejected" }).handler + ); + + const rejectedApp = await renderApp({ skProps }); + + await expect + .element(rejectedApp.getByTestId("kyc-gate-card-rejected")) + .toBeInTheDocument(); + await expect + .element(rejectedApp.getByText("Verification not approved")) + .toBeInTheDocument(); + + await rejectedApp.unmount(); + + worker.use( + ...mockKycRequiredDefaultYield(), + http.get(yieldApiRoute("/v1/yields/:yieldId/kyc/status"), async () => { + await mockDelay(); + + return HttpResponse.json( + { message: "Unable to fetch KYC status" }, + { status: 500 } + ); + }) + ); + + const unknownApp = await renderApp({ skProps }); + + await expect + .element(unknownApp.getByTestId("kyc-gate-card-unknown")) + .toBeInTheDocument(); + await expect + .element(unknownApp.getByText("Verification status unavailable")) + .toBeInTheDocument(); + + await unknownApp.unmount(); + }); + + it("does not show a card for approved or not required status", async ({ + worker, + }) => { + worker.use( + ...mockKycRequiredDefaultYield(), + mockKycStatus({ status: "approved" }).handler + ); + + const approvedApp = await renderApp({ skProps }); + + await expect + .poll(() => approvedApp.getByTestId(/^kyc-gate-card/).elements().length) + .toBe(0); + + await approvedApp.unmount(); + + worker.use( + ...mockKycRequiredDefaultYield(), + mockKycStatus({ status: "not_required" }).handler + ); + + const notRequiredApp = await renderApp({ skProps }); + + await expect + .poll( + () => notRequiredApp.getByTestId(/^kyc-gate-card/).elements().length + ) + .toBe(0); + + await notRequiredApp.unmount(); + }); + + it("keeps enter action 412 KYC failures on the global error path", async ({ + worker, + }) => { + const { customConnectors } = await setupStakingFlow(worker); + + const kycStatus = mockKycStatus({ status: "approved" }); + + worker.use( + kycStatus.handler, + http.post(yieldApiRoute("/v1/actions/enter"), async () => { + await mockDelay(); + + return HttpResponse.json( + { + message: "KYC required", + details: { + protocol: "Superstate", + authorizeUrl: "https://issuer.example/verify", + }, + }, + { status: 412 } + ); + }) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: customConnectors, + forceWalletConnectOnly: false, + }, + }); + + await expect + .element(app.getByText(formatAddress(account))) + .toBeInTheDocument(); + + await app.getByTestId("select-opportunity").click(); + + await app + .getByTestId("select-modal__container") + .getByTestId(/^select-opportunity__item_avalanche-avax-liquid-staking/) + .click(); + + await userEvent.click(app.getByTestId("number-input")); + await userEvent.keyboard("0.1"); + await userEvent.click(app.getByText("Stake").last()); + + await expect.element(app.getByText("KYC required")).toBeInTheDocument(); + await expect + .poll(() => app.getByTestId("kyc-gate-card-start_kyc").elements().length) + .toBe(0); + + await app.unmount(); + }); + + it("keeps exit action 412 KYC failures on the global error path", async ({ + worker, + }) => { + const { account, customConnectors, legacyYield, setUrl } = + await setupTrustPosition(worker); + + setUrl({ + accountId: account, + balanceId: "default", + yieldId: legacyYield.id, + }); + + const kycStatus = mockKycStatus({ status: "approved" }); + + worker.use( + kycStatus.handler, + http.post(yieldApiRoute("/v1/actions/exit"), async () => { + await mockDelay(); + + return HttpResponse.json( + { + message: "KYC required", + details: { + protocol: "Superstate", + authorizeUrl: "https://issuer.example/verify", + }, + }, + { status: 412 } + ); + }) + ); + + const app = await renderApp({ + wagmi: { + __customConnectors__: customConnectors, + }, + }); + + await expect.element(app.getByText("Trust USDA Earn")).toBeInTheDocument(); + + const withdrawButton = app + .getByRole("button", { exact: true, name: "Withdraw" }) + .last(); + + await expect.element(withdrawButton).toBeEnabled(); + await userEvent.click(withdrawButton); + + await expect.element(app.getByText("KYC required")).toBeInTheDocument(); + await expect + .poll(() => app.getByTestId("kyc-gate-card-start_kyc").elements().length) + .toBe(0); + + await app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx b/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx deleted file mode 100644 index 17ea290d6..000000000 --- a/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx +++ /dev/null @@ -1,423 +0,0 @@ -import { HttpResponse, http } from "msw"; -import { mainnet } from "viem/chains"; -import { userEvent } from "vitest/browser"; -import type { SKAppProps } from "../../src/App"; -import { KycGateCard } from "../../src/components/molecules/kyc-gate-card"; -import { formatAddress } from "../../src/utils"; -import { yieldApiYieldFixture } from "../fixtures"; -import { legacyApiRoute, yieldApiRoute } from "../mocks/api-routes"; -import { mockDelay } from "../mocks/delay"; -import { describe, expect, it, vi } from "../utils/test-extend"; -import { render, renderApp } from "../utils/test-utils"; -import { setup as setupStakingFlow } from "./staking-flow/setup"; -import { setup as setupTrustPosition } from "./trust-incentive-apy/setup"; - -const account = "0xB6c5273e79E2aDD234EBC07d87F3824e0f94B2F7"; - -const skProps = { - apiKey: import.meta.env.VITE_API_KEY, - externalProviders: { - type: "generic", - provider: { - signMessage: async () => "signature", - switchChain: async () => {}, - sendTransaction: async () => "hash", - }, - currentAddress: account, - currentChain: mainnet.id, - supportedChainIds: [mainnet.id], - }, -} satisfies SKAppProps; - -const mockKycStatus = ({ - status, - statusAfterRefresh, -}: { - status: string; - statusAfterRefresh?: string; -}) => { - let calls = 0; - let requestedAddress: string | null = null; - - const handler = http.get( - yieldApiRoute("/v1/yields/:yieldId/kyc/status"), - async ({ request }) => { - await mockDelay(); - calls += 1; - requestedAddress = new URL(request.url).searchParams.get("address"); - - return HttpResponse.json({ - kycStatus: - calls > 1 && statusAfterRefresh ? statusAfterRefresh : status, - kycUrl: "https://issuer.example/verify", - }); - } - ); - - return { - handler, - getCalls: () => calls, - getRequestedAddress: () => requestedAddress, - }; -}; - -const mockKycRequiredDefaultYield = () => { - const baseYield = yieldApiYieldFixture(); - const kycRequiredYield = yieldApiYieldFixture({ - ...baseYield, - mechanics: { - ...baseYield.mechanics, - requirements: { - kycRequired: true, - kyc: { - kycMode: "oauth_redirect", - iframeAllowed: false, - authorizeUrl: "https://issuer.example/verify", - eligibility: { - defaultPolicy: "allow", - countries: [], - blockedCountries: [], - blockedSubdivisions: [], - usPersonAllowed: true, - investorEligibility: [], - subjectTypes: ["KYC"], - }, - }, - }, - }, - }); - - return [ - http.post(legacyApiRoute("/v1/tokens/balances/scan"), async () => { - await mockDelay(); - - return HttpResponse.json([ - { - token: baseYield.token, - amount: "1", - availableYields: [baseYield.id], - }, - ]); - }), - http.post(legacyApiRoute("/v1/tokens/balances"), async () => { - await mockDelay(); - - return HttpResponse.json([ - { - token: baseYield.token, - amount: "1", - availableYields: [baseYield.id], - }, - ]); - }), - http.get(yieldApiRoute("/v1/yields"), async () => { - await mockDelay(); - - return HttpResponse.json({ - items: [kycRequiredYield], - total: 1, - offset: 0, - limit: 1, - }); - }), - http.get(yieldApiRoute(`/v1/yields/${baseYield.id}`), async () => { - await mockDelay(); - - return HttpResponse.json(kycRequiredYield); - }), - ]; -}; - -describe("RWA KYC flow", () => { - it("shows not started verification card and fetches wallet status", async ({ - worker, - }) => { - const kycStatus = mockKycStatus({ status: "not_started" }); - - worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); - - const app = await renderApp({ skProps }); - - await expect - .element(app.getByTestId("kyc-gate-card-start_kyc")) - .toBeInTheDocument(); - await expect - .element(app.getByText("Identity verification required")) - .toBeInTheDocument(); - await expect.poll(kycStatus.getRequestedAddress).toBe(account); - - await app.unmount(); - }); - - it("opens iframe modal for iframe-allowed verification and refreshes status on close", async () => { - const onCheckStatus = vi.fn(); - const app = await render( - - ); - - await expect - .element(app.getByTestId("kyc-gate-card-start_kyc")) - .toBeInTheDocument(); - - await userEvent.click(app.getByTestId("kyc-gate-verify")); - - await expect - .element(app.getByTestId("kyc-verification-modal")) - .toBeInTheDocument(); - await expect - .element(app.getByTestId("kyc-verification-iframe")) - .toHaveAttribute("src", "https://issuer.example/verify"); - - await userEvent.click(app.getByTestId("kyc-verification-close")); - - expect(onCheckStatus).toHaveBeenCalledOnce(); - await expect - .poll(() => app.getByTestId(/^kyc-gate-card/).elements().length) - .toBe(1); - - await app.unmount(); - }); - - it("opens verification externally when iframe is not allowed", async ({ - worker, - }) => { - const openSpy = vi.spyOn(window, "open").mockImplementation(() => null); - const kycStatus = mockKycStatus({ status: "not_started" }); - - worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); - - const app = await renderApp({ skProps }); - - await expect - .element(app.getByTestId("kyc-gate-card-start_kyc")) - .toBeInTheDocument(); - - await userEvent.click(app.getByTestId("kyc-gate-verify")); - - expect(openSpy).toHaveBeenCalledWith( - "https://issuer.example/verify", - "_blank", - "noopener,noreferrer" - ); - await expect - .poll(() => app.getByTestId("kyc-verification-modal").elements().length) - .toBe(0); - - openSpy.mockRestore(); - await app.unmount(); - }); - - it("refreshes pending status with Check status", async ({ worker }) => { - const kycStatus = mockKycStatus({ - status: "pending", - statusAfterRefresh: "approved", - }); - - worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler); - - const app = await renderApp({ skProps }); - - await expect - .element(app.getByTestId("kyc-gate-card-pending")) - .toBeInTheDocument(); - - await userEvent.click(app.getByTestId("kyc-gate-check-status")); - - await expect - .poll(() => app.getByTestId(/^kyc-gate-card/).elements().length) - .toBe(0); - - await app.unmount(); - }); - - it("shows rejected and unknown states", async ({ worker }) => { - worker.use( - ...mockKycRequiredDefaultYield(), - mockKycStatus({ status: "rejected" }).handler - ); - - const rejectedApp = await renderApp({ skProps }); - - await expect - .element(rejectedApp.getByTestId("kyc-gate-card-rejected")) - .toBeInTheDocument(); - await expect - .element(rejectedApp.getByText("Verification not approved")) - .toBeInTheDocument(); - - await rejectedApp.unmount(); - - worker.use( - ...mockKycRequiredDefaultYield(), - http.get(yieldApiRoute("/v1/yields/:yieldId/kyc/status"), async () => { - await mockDelay(); - - return HttpResponse.json( - { message: "Unable to fetch KYC status" }, - { status: 500 } - ); - }) - ); - - const unknownApp = await renderApp({ skProps }); - - await expect - .element(unknownApp.getByTestId("kyc-gate-card-unknown")) - .toBeInTheDocument(); - await expect - .element(unknownApp.getByText("Verification status unavailable")) - .toBeInTheDocument(); - - await unknownApp.unmount(); - }); - - it("does not show a card for approved or not required status", async ({ - worker, - }) => { - worker.use( - ...mockKycRequiredDefaultYield(), - mockKycStatus({ status: "approved" }).handler - ); - - const approvedApp = await renderApp({ skProps }); - - await expect - .poll(() => approvedApp.getByTestId(/^kyc-gate-card/).elements().length) - .toBe(0); - - await approvedApp.unmount(); - - worker.use( - ...mockKycRequiredDefaultYield(), - mockKycStatus({ status: "not_required" }).handler - ); - - const notRequiredApp = await renderApp({ skProps }); - - await expect - .poll( - () => notRequiredApp.getByTestId(/^kyc-gate-card/).elements().length - ) - .toBe(0); - - await notRequiredApp.unmount(); - }); - - it("keeps enter action 412 KYC failures on the global error path", async ({ - worker, - }) => { - const { customConnectors } = await setupStakingFlow(worker); - - const kycStatus = mockKycStatus({ status: "approved" }); - - worker.use( - kycStatus.handler, - http.post(yieldApiRoute("/v1/actions/enter"), async () => { - await mockDelay(); - - return HttpResponse.json( - { - message: "KYC required", - details: { - protocol: "Superstate", - authorizeUrl: "https://issuer.example/verify", - }, - }, - { status: 412 } - ); - }) - ); - - const app = await renderApp({ - wagmi: { - __customConnectors__: customConnectors, - forceWalletConnectOnly: false, - }, - }); - - await expect - .element(app.getByText(formatAddress(account))) - .toBeInTheDocument(); - - await app.getByTestId("select-opportunity").click(); - - await app - .getByTestId("select-modal__container") - .getByTestId(/^select-opportunity__item_avalanche-avax-liquid-staking/) - .click(); - - await userEvent.click(app.getByTestId("number-input")); - await userEvent.keyboard("0.1"); - await userEvent.click(app.getByText("Stake").last()); - - await expect.element(app.getByText("KYC required")).toBeInTheDocument(); - await expect - .poll(() => app.getByTestId("kyc-gate-card-start_kyc").elements().length) - .toBe(0); - - await app.unmount(); - }); - - it("keeps exit action 412 KYC failures on the global error path", async ({ - worker, - }) => { - const { account, customConnectors, legacyYield, setUrl } = - await setupTrustPosition(worker); - - setUrl({ - accountId: account, - balanceId: "default", - yieldId: legacyYield.id, - }); - - const kycStatus = mockKycStatus({ status: "approved" }); - - worker.use( - kycStatus.handler, - http.post(yieldApiRoute("/v1/actions/exit"), async () => { - await mockDelay(); - - return HttpResponse.json( - { - message: "KYC required", - details: { - protocol: "Superstate", - authorizeUrl: "https://issuer.example/verify", - }, - }, - { status: 412 } - ); - }) - ); - - const app = await renderApp({ - wagmi: { - __customConnectors__: customConnectors, - }, - }); - - await expect.element(app.getByText("Trust USDA Earn")).toBeInTheDocument(); - - const withdrawButton = app - .getByRole("button", { exact: true, name: "Withdraw" }) - .last(); - - await expect.element(withdrawButton).toBeEnabled(); - await userEvent.click(withdrawButton); - - await expect.element(app.getByText("KYC required")).toBeInTheDocument(); - await expect - .poll(() => app.getByTestId("kyc-gate-card-start_kyc").elements().length) - .toBe(0); - - await app.unmount(); - }); -}); diff --git a/packages/widget/tests/use-cases/select-opportunity.browser.test.tsx b/packages/widget/tests/use-cases/select-opportunity.browser.test.tsx new file mode 100644 index 000000000..beac1e517 --- /dev/null +++ b/packages/widget/tests/use-cases/select-opportunity.browser.test.tsx @@ -0,0 +1,288 @@ +import { Array as EArray } from "effect"; +import { HttpResponse, http } from "msw"; +import { userEvent } from "vitest/browser"; +import { + legacyYieldFixture, + yieldApiYieldDtoFixture, + yieldRiskSummaryFixture, +} from "../fixtures"; +import { legacyApiRoute, yieldApiRoute } from "../mocks/api-routes"; +import { mockDelay } from "../mocks/delay"; +import { describe, expect, it } from "../utils/test-extend"; +import { renderApp } from "../utils/test-utils"; + +type LegacyTokenDto = ReturnType["token"]; + +describe("Select opportunity", () => { + it("Works as expected", async ({ worker }) => { + window.history.pushState({}, "", "/"); + + const token: LegacyTokenDto = { + network: "ethereum", + name: "Ethereum", + symbol: "ETH", + decimals: 18, + coinGeckoId: "ethereum", + logoURI: "https://assets.stakek.it/tokens/eth.svg", + }; + const yieldIds = [ + "ethereum-eth-lido-staking", + "ethereum-eth-stakewise-staking", + "ethereum-eth-reth-staking", + ] as const; + + const legacyYieldBase = legacyYieldFixture(); + const yieldApiYieldBase = yieldApiYieldDtoFixture(); + const getRewardToken = (integrationId: (typeof yieldIds)[number]) => { + switch (integrationId) { + case "ethereum-eth-reth-staking": + return { + ...token, + address: "0xae78736cd615f374d3085123a210448e74fc6393", + name: "Rocket Pool ETH", + symbol: "rETH", + }; + case "ethereum-eth-lido-staking": + return { + ...token, + address: "0xae7ab96520de3a18e5e111b5eaab095312d7fe84", + name: "Lido Staked ETH", + symbol: "stETH", + }; + default: + return { + ...token, + address: "0x0000000000000000000000000000000000000001", + name: "Banana ETH", + symbol: "bananaETH", + }; + } + }; + const getLegacyYield = (integrationId: (typeof yieldIds)[number]) => { + const rewardToken = getRewardToken(integrationId); + + return legacyYieldFixture({ + id: integrationId, + args: { enter: { args: { nfts: undefined } } }, + token, + metadata: { + ...legacyYieldBase.metadata, + type: "liquid-staking", + rewardTokens: [rewardToken], + provider: { + id: "stakewise", + name: "Stakewise", + description: "", + externalLink: "https://stakewise.io", + logoURI: + "https://assets.stakek.it/app/composition/providers/stakewise.svg", + }, + }, + status: { + ...legacyYieldBase.status, + enter: integrationId !== "ethereum-eth-stakewise-staking", + }, + }); + }; + const getYieldApiYield = (integrationId: (typeof yieldIds)[number]) => { + const rewardToken = getRewardToken(integrationId); + + return yieldApiYieldDtoFixture({ + id: integrationId, + network: token.network, + providerId: "stakewise", + token, + tokens: [token], + inputTokens: [token], + outputToken: rewardToken, + rewardRate: { + ...yieldApiYieldBase.rewardRate, + components: [ + { + rate: yieldApiYieldBase.rewardRate.total, + rateType: yieldApiYieldBase.rewardRate.rateType, + token: rewardToken, + yieldSource: "staking", + }, + ], + }, + status: { + ...yieldApiYieldBase.status, + enter: integrationId !== "ethereum-eth-stakewise-staking", + }, + metadata: { + ...yieldApiYieldBase.metadata, + name: legacyYieldBase.metadata.name, + }, + mechanics: { + ...yieldApiYieldBase.mechanics, + type: "staking", + gasFeeToken: token, + }, + risk: + integrationId === "ethereum-eth-reth-staking" + ? yieldRiskSummaryFixture({ ratings: [] }) + : yieldRiskSummaryFixture({ + ratings: [ + { + rating: + integrationId === "ethereum-eth-lido-staking" + ? "A-" + : "B-", + source: + integrationId === "ethereum-eth-lido-staking" + ? "credora" + : "stakingRewards", + }, + ], + }), + }); + }; + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + + return HttpResponse.json(["ethereum", "polkadot"]); + }), + http.get(legacyApiRoute("/v1/tokens"), async () => { + await mockDelay(); + return HttpResponse.json([ + { + token, + availableYields: [ + "ethereum-eth-lido-staking", + "ethereum-eth-stakewise-staking", + "ethereum-eth-reth-staking", + ], + }, + ]); + }), + http.get(yieldApiRoute("/v1/yields"), async () => { + await mockDelay(); + + const items = yieldIds.map((integrationId) => + getYieldApiYield(integrationId) + ); + + return HttpResponse.json({ + items, + total: items.length, + offset: 0, + limit: items.length, + }); + }), + + ...yieldIds.flatMap((integrationId) => { + const legacyYield = getLegacyYield(integrationId); + + return [ + http.get(legacyApiRoute(`/v1/yields/${integrationId}`), async () => { + await mockDelay(); + return HttpResponse.json(legacyYield); + }), + http.get(yieldApiRoute(`/v1/yields/${integrationId}`), async () => { + await mockDelay(); + return HttpResponse.json(getYieldApiYield(integrationId)); + }), + ]; + }) + ); + + const app = await renderApp(); + const clickOpportunity = async (id: (typeof yieldIds)[number]) => { + const item = app + .getByTestId("select-modal__container") + .getByTestId(new RegExp(`^select-opportunity__item_${id}`)); + + await expect.element(item).toBeInTheDocument(); + await expect.poll(() => item.elements()[0]).toBeTruthy(); + await userEvent.click(EArray.getUnsafe(item.elements(), 0)); + }; + const clickText = async (text: string) => { + const item = app.getByText(text).first(); + + await expect.element(item).toBeInTheDocument(); + await expect.poll(() => item.elements()[0]).toBeTruthy(); + (item.elements()[0] as HTMLElement).click(); + }; + + await app.getByTestId("select-opportunity").click(); + + let selectContainer = app.getByTestId("select-modal__container"); + + await expect + .element(selectContainer.getByTestId("select-modal__search-input")) + .toBeInTheDocument(); + await expect + .element(selectContainer.getByTestId("select-modal__title")) + .toBeInTheDocument(); + + selectContainer = app.getByTestId("select-modal__container"); + + await expect + .element( + selectContainer.getByTestId( + /^select-opportunity__item_ethereum-eth-lido-staking/ + ) + ) + .toBeInTheDocument(); + + await expect + .element( + selectContainer.getByTestId( + /^select-opportunity__item_ethereum-eth-reth-staking/ + ) + ) + .toBeInTheDocument(); + + await expect + .element( + selectContainer.getByTestId( + /^select-opportunity__item_ethereum-eth-stakewise-staking/ + ) + ) + .not.toBeInTheDocument(); + + await clickOpportunity("ethereum-eth-reth-staking"); + + await expect + .element(app.getByText("You'll receive").first()) + .toBeInTheDocument(); + await expect.element(app.getByText("rETH").first()).toBeInTheDocument(); + await expect + .element(app.getByTestId("yield-risk-rating-summary")) + .not.toBeInTheDocument(); + + await expect.element(app.getByText("Connect Wallet")).toBeInTheDocument(); + + await app.getByText("Connect Wallet").click(); + + await expect.element(app.getByText("Select a Chain")).toBeInTheDocument(); + + await clickText("EVM"); + + await expect.element(app.getByText("Connect a Wallet")).toBeInTheDocument(); + + await userEvent.keyboard("[Escape]"); + + await app.getByTestId("select-opportunity").click(); + + selectContainer = app.getByTestId("select-modal__container"); + + await clickOpportunity("ethereum-eth-lido-staking"); + + await expect + .element(app.getByText("You'll receive").first()) + .toBeInTheDocument(); + await expect.element(app.getByText("stETH").first()).toBeInTheDocument(); + await expect.element(app.getByText("Rated by Credora")).toBeInTheDocument(); + await expect + .element( + app.getByTestId("yield-risk-rating-summary__badge").getByText("A-") + ) + .toBeInTheDocument(); + + app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/select-opportunity.test.tsx b/packages/widget/tests/use-cases/select-opportunity.test.tsx deleted file mode 100644 index 9ee0c90be..000000000 --- a/packages/widget/tests/use-cases/select-opportunity.test.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import { HttpResponse, http } from "msw"; -import { userEvent } from "vitest/browser"; -import { - legacyYieldFixture, - yieldApiYieldFixture, - yieldRiskSummaryFixture, -} from "../fixtures"; -import { legacyApiRoute, yieldApiRoute } from "../mocks/api-routes"; -import { mockDelay } from "../mocks/delay"; -import { describe, expect, it } from "../utils/test-extend"; -import { renderApp } from "../utils/test-utils"; - -type LegacyTokenDto = ReturnType["token"]; - -describe("Select opportunity", () => { - it("Works as expected", async ({ worker }) => { - window.history.pushState({}, "", "/"); - - const token: LegacyTokenDto = { - network: "ethereum", - name: "Ethereum", - symbol: "ETH", - decimals: 18, - coinGeckoId: "ethereum", - logoURI: "https://assets.stakek.it/tokens/eth.svg", - }; - const yieldIds = [ - "ethereum-eth-lido-staking", - "ethereum-eth-stakewise-staking", - "ethereum-eth-reth-staking", - ] as const; - - const legacyYieldBase = legacyYieldFixture(); - const yieldApiYieldBase = yieldApiYieldFixture(); - const getRewardToken = (integrationId: (typeof yieldIds)[number]) => { - switch (integrationId) { - case "ethereum-eth-reth-staking": - return { - ...token, - address: "0xae78736cd615f374d3085123a210448e74fc6393", - name: "Rocket Pool ETH", - symbol: "rETH", - }; - case "ethereum-eth-lido-staking": - return { - ...token, - address: "0xae7ab96520de3a18e5e111b5eaab095312d7fe84", - name: "Lido Staked ETH", - symbol: "stETH", - }; - default: - return { - ...token, - address: "0x0000000000000000000000000000000000000001", - name: "Banana ETH", - symbol: "bananaETH", - }; - } - }; - const getLegacyYield = (integrationId: (typeof yieldIds)[number]) => { - const rewardToken = getRewardToken(integrationId); - - return legacyYieldFixture({ - id: integrationId, - args: { enter: { args: { nfts: undefined } } }, - token, - metadata: { - ...legacyYieldBase.metadata, - type: "liquid-staking", - rewardTokens: [rewardToken], - provider: { - id: "stakewise", - name: "Stakewise", - description: "", - externalLink: "https://stakewise.io", - logoURI: "https://assets.stakek.it/providers/stakewise.svg", - }, - }, - status: { - ...legacyYieldBase.status, - enter: integrationId !== "ethereum-eth-stakewise-staking", - }, - }); - }; - const getYieldApiYield = (integrationId: (typeof yieldIds)[number]) => { - const rewardToken = getRewardToken(integrationId); - - return yieldApiYieldFixture({ - id: integrationId, - network: token.network, - providerId: "stakewise", - token, - tokens: [token], - inputTokens: [token], - outputToken: rewardToken, - rewardRate: { - ...yieldApiYieldBase.rewardRate, - components: [ - { - rate: yieldApiYieldBase.rewardRate.total, - rateType: yieldApiYieldBase.rewardRate.rateType, - token: rewardToken, - yieldSource: "staking", - }, - ], - }, - status: { - ...yieldApiYieldBase.status, - enter: integrationId !== "ethereum-eth-stakewise-staking", - }, - metadata: { - ...yieldApiYieldBase.metadata, - name: legacyYieldBase.metadata.name, - }, - mechanics: { - ...yieldApiYieldBase.mechanics, - type: "staking", - gasFeeToken: token, - }, - risk: - integrationId === "ethereum-eth-reth-staking" - ? yieldRiskSummaryFixture({ ratings: [] }) - : yieldRiskSummaryFixture({ - ratings: [ - { - rating: - integrationId === "ethereum-eth-lido-staking" - ? "A-" - : "B-", - source: - integrationId === "ethereum-eth-lido-staking" - ? "credora" - : "stakingRewards", - }, - ], - }), - }); - }; - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - - return HttpResponse.json(["ethereum", "polkadot"]); - }), - http.get(legacyApiRoute("/v1/tokens"), async () => { - await mockDelay(); - return HttpResponse.json([ - { - token, - availableYields: [ - "ethereum-eth-lido-staking", - "ethereum-eth-stakewise-staking", - "ethereum-eth-reth-staking", - ], - }, - ]); - }), - http.get(yieldApiRoute("/v1/yields"), async () => { - await mockDelay(); - - const items = yieldIds.map((integrationId) => - getYieldApiYield(integrationId) - ); - - return HttpResponse.json({ - items, - total: items.length, - offset: 0, - limit: items.length, - }); - }), - - ...yieldIds.flatMap((integrationId) => { - const legacyYield = getLegacyYield(integrationId); - - return [ - http.get(legacyApiRoute(`/v1/yields/${integrationId}`), async () => { - await mockDelay(); - return HttpResponse.json(legacyYield); - }), - http.get(yieldApiRoute(`/v1/yields/${integrationId}`), async () => { - await mockDelay(); - return HttpResponse.json(getYieldApiYield(integrationId)); - }), - ]; - }) - ); - - const app = await renderApp(); - const clickOpportunity = async (id: (typeof yieldIds)[number]) => { - const item = app - .getByTestId("select-modal__container") - .getByTestId(new RegExp(`^select-opportunity__item_${id}`)); - - await expect.element(item).toBeInTheDocument(); - await expect.poll(() => item.elements()[0]).toBeTruthy(); - await userEvent.click(item.elements()[0]); - }; - const clickText = async (text: string) => { - const item = app.getByText(text).first(); - - await expect.element(item).toBeInTheDocument(); - await expect.poll(() => item.elements()[0]).toBeTruthy(); - (item.elements()[0] as HTMLElement).click(); - }; - - await app.getByTestId("select-opportunity").click(); - - let selectContainer = app.getByTestId("select-modal__container"); - - await expect - .element(selectContainer.getByTestId("select-modal__search-input")) - .toBeInTheDocument(); - await expect - .element(selectContainer.getByTestId("select-modal__title")) - .toBeInTheDocument(); - - selectContainer = app.getByTestId("select-modal__container"); - - await expect - .element( - selectContainer.getByTestId( - /^select-opportunity__item_ethereum-eth-lido-staking/ - ) - ) - .toBeInTheDocument(); - - await expect - .element( - selectContainer.getByTestId( - /^select-opportunity__item_ethereum-eth-reth-staking/ - ) - ) - .toBeInTheDocument(); - - await expect - .element( - selectContainer.getByTestId( - /^select-opportunity__item_ethereum-eth-stakewise-staking/ - ) - ) - .not.toBeInTheDocument(); - - await clickOpportunity("ethereum-eth-reth-staking"); - - await expect - .element(app.getByText("You'll receive").first()) - .toBeInTheDocument(); - await expect.element(app.getByText("rETH").first()).toBeInTheDocument(); - await expect - .element(app.getByTestId("yield-risk-rating-summary")) - .not.toBeInTheDocument(); - - await expect.element(app.getByText("Connect Wallet")).toBeInTheDocument(); - - await app.getByText("Connect Wallet").click(); - - await expect.element(app.getByText("Select a Chain")).toBeInTheDocument(); - - await clickText("EVM"); - - await expect.element(app.getByText("Connect a Wallet")).toBeInTheDocument(); - - await userEvent.keyboard("[Escape]"); - - await app.getByTestId("select-opportunity").click(); - - selectContainer = app.getByTestId("select-modal__container"); - - await clickOpportunity("ethereum-eth-lido-staking"); - - await expect - .element(app.getByText("You'll receive").first()) - .toBeInTheDocument(); - await expect.element(app.getByText("stETH").first()).toBeInTheDocument(); - await expect.element(app.getByText("Rated by Credora")).toBeInTheDocument(); - await expect - .element( - app.getByTestId("yield-risk-rating-summary__badge").getByText("A-") - ) - .toBeInTheDocument(); - - app.unmount(); - }); -}); diff --git a/packages/widget/tests/use-cases/sk-wallet.dom.test.tsx b/packages/widget/tests/use-cases/sk-wallet.dom.test.tsx new file mode 100644 index 000000000..4048a316e --- /dev/null +++ b/packages/widget/tests/use-cases/sk-wallet.dom.test.tsx @@ -0,0 +1,387 @@ +import { useAtomSet } from "@effect/atom-react"; +import { + Address, + beginCell, + type CommonMessageInfoRelaxedInternal, + internal, + storeMessageRelaxed, +} from "@ton/core"; +import { Schema } from "effect"; +import { HttpResponse, http } from "msw"; +import { ThirdPartyQueryClientProvider } from "../../src/app/composition/providers/query-client"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { walletRuntime } from "../../src/app/runtime/wallet-runtime"; +import { ActionId, TransactionId } from "../../src/domain/schema/identifiers"; +import { solana, ton } from "../../src/domain/types/chains/misc"; +import { MiscNetworks } from "../../src/domain/types/chains/networks"; +import { WagmiConfigProvider } from "../../src/features/wallet/react/provider"; +import { useSKWallet } from "../../src/features/wallet/react/use-wallet"; +import type { SKExternalProviders, SKTxMeta } from "../../src/public-api/types"; +import type { WalletSignTransactionInput } from "../../src/services/wallet/domain/transactions"; +import { WalletService } from "../../src/services/wallet/wallet-service"; +import { legacyApiRoute } from "../mocks/api-routes"; +import { mockDelay } from "../mocks/delay"; +import { TestAtomRuntimeProvider } from "../utils/atom-runtime-provider"; +import { describe, expect, it, vi } from "../utils/test-extend.dom"; +import { renderHook } from "../utils/test-utils.dom"; + +const signTransactionAtom = walletRuntime.fn( + (input: WalletSignTransactionInput) => + WalletService.use((wallet) => wallet.signTransaction(input)) +); + +const useTestWallet = () => { + const wallet = useSKWallet(); + const signTransaction = useAtomSet(signTransactionAtom, { mode: "promise" }); + + return { ...wallet, signTransaction }; +}; + +const renderHookWithExternalProvider = ( + externalProviders: SKExternalProviders, + options: { + variant?: "default" | "utila"; + } = {} +) => + renderHook(useTestWallet, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); + +const waitForWalletConnection = ( + wallet: Awaited> +) => + wallet.act(async () => { + await expect + .poll( + () => + !wallet.result.current.isConnecting && + wallet.result.current.isConnected + ) + .toBe(true); + }); + +const createSolanaTxMeta = (): SKTxMeta => ({ + txId: Schema.decodeSync(TransactionId)("transaction-id"), + actionId: Schema.decodeSync(ActionId)("action-id"), + actionType: "STAKE", + txType: "APPROVAL", + amount: "100", + inputToken: { + decimals: 0, + symbol: "", + name: "", + network: "solana", + }, + structuredTransaction: null, + annotatedTransaction: null, + providersDetails: [], +}); + +const createTonTxMeta = (): SKTxMeta => ({ + txId: Schema.decodeSync(TransactionId)("transaction-id"), + actionId: Schema.decodeSync(ActionId)("action-id"), + actionType: "STAKE", + txType: "APPROVAL", + amount: "100", + inputToken: { + decimals: 0, + symbol: "", + name: "", + network: "ton", + }, + structuredTransaction: null, + annotatedTransaction: null, + providersDetails: [], +}); + +const createDefaultTonTransactionFixture = () => { + const message = internal({ + to: Address.parseRaw( + "0:0000000000000000000000000000000000000000000000000000000000000000" + ), + value: 123n, + body: "Deposit", + }); + const info = message.info as CommonMessageInfoRelaxedInternal; + + return { + tx: JSON.stringify({ + seqno: 0, + message: beginCell() + .store(storeMessageRelaxed(message)) + .endCell() + .toBoc() + .toString("base64"), + }), + rawTx: [ + { + address: info.dest.toString(), + amount: info.value.coins.toString(), + payload: message.body.toBoc().toString("base64"), + }, + ], + }; +}; + +describe("SK Wallet", () => { + it("should work with solana external provider", async ({ worker }) => { + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async () => "hash"); + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([MiscNetworks.Solana]); + }) + ); + + const solanaWallet = await renderHookWithExternalProvider({ + type: "generic", + currentAddress: "9TCnDo7Txc5bC9SnE9iKsU5CyffLfeK4nrv1BFUmxkiJ", + currentChain: solana.id, + supportedChainIds: [solana.id], + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + }); + + await waitForWalletConnection(solanaWallet); + + const solanaRes = await solanaWallet.result.current.signTransaction({ + network: "solana", + tx: "AQIDBA==", + txMeta: createSolanaTxMeta(), + ledgerHwAppId: null, + }); + + expect(solanaRes).toEqual({ + signedTx: "hash", + broadcasted: true, + }); + expect(sendTransactionSpy).toHaveBeenCalledWith( + { + type: "solana", + tx: "01020304", + }, + createSolanaTxMeta() + ); + }); + + it("keeps hex solana external provider transactions in hex form", async ({ + worker, + }) => { + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async () => "hash"); + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([MiscNetworks.Solana]); + }) + ); + + const solanaWallet = await renderHookWithExternalProvider({ + type: "generic", + currentAddress: "9TCnDo7Txc5bC9SnE9iKsU5CyffLfeK4nrv1BFUmxkiJ", + currentChain: solana.id, + supportedChainIds: [solana.id], + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + }); + + await waitForWalletConnection(solanaWallet); + + const solanaRes = await solanaWallet.result.current.signTransaction({ + network: "solana", + tx: "0xA1B2", + txMeta: createSolanaTxMeta(), + ledgerHwAppId: null, + }); + + expect(solanaRes).toEqual({ + signedTx: "hash", + broadcasted: true, + }); + expect(sendTransactionSpy).toHaveBeenCalledWith( + { + type: "solana", + tx: "a1b2", + }, + createSolanaTxMeta() + ); + }); + + it("preserves custom external provider transaction errors", async ({ + worker, + }) => { + const customMessage = "Transaction blocked by policy"; + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async () => ({ + type: "error" as const, + error: customMessage, + })); + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([MiscNetworks.Solana]); + }) + ); + + const solanaWallet = await renderHookWithExternalProvider({ + type: "generic", + currentAddress: "9TCnDo7Txc5bC9SnE9iKsU5CyffLfeK4nrv1BFUmxkiJ", + currentChain: solana.id, + supportedChainIds: [solana.id], + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + }); + + await waitForWalletConnection(solanaWallet); + + const solanaRes = solanaWallet.result.current.signTransaction({ + network: "solana", + tx: "12345", + txMeta: { + txId: Schema.decodeSync(TransactionId)("transaction-id"), + actionId: Schema.decodeSync(ActionId)("action-id"), + actionType: "STAKE", + txType: "APPROVAL", + amount: "100", + inputToken: { + decimals: 0, + symbol: "", + name: "", + network: "solana", + }, + structuredTransaction: null, + annotatedTransaction: null, + providersDetails: [], + }, + ledgerHwAppId: null, + }); + + await expect(solanaRes).rejects.toMatchObject({ + _tag: "WalletBroadcastError", + customMessage, + }); + }); + + it("should work with ton external provider", async ({ worker }) => { + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async (_: unknown) => "hash"); + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([MiscNetworks.Ton]); + }) + ); + + const tonWallet = await renderHookWithExternalProvider({ + type: "generic", + currentAddress: "UQDyiNAyPy8QRQy45-SjxzrbKVOTOVyXaVGPZSLI9jxHF_Sy", + currentChain: ton.id, + supportedChainIds: [ton.id], + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + }); + await waitForWalletConnection(tonWallet); + + const tonFixture = createDefaultTonTransactionFixture(); + const tonRes = await tonWallet.result.current.signTransaction({ + network: "ton", + tx: tonFixture.tx, + txMeta: createTonTxMeta(), + ledgerHwAppId: null, + }); + + expect(tonRes).toEqual({ + signedTx: "hash", + broadcasted: true, + }); + expect(sendTransactionSpy).toHaveBeenCalledWith( + { + type: "ton", + tx: tonFixture.rawTx, + }, + createTonTxMeta() + ); + }); + + it("keeps raw ton transactions unchanged for external provider", async ({ + worker, + }) => { + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async (_: unknown) => "hash"); + const rawTx = [ + { + address: "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", + amount: "123", + payload: "te6cckEBAQEAAgAAAA==", + }, + ]; + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await mockDelay(); + return HttpResponse.json([MiscNetworks.Ton]); + }) + ); + + const tonWallet = await renderHookWithExternalProvider({ + type: "generic", + currentAddress: "UQDyiNAyPy8QRQy45-SjxzrbKVOTOVyXaVGPZSLI9jxHF_Sy", + currentChain: ton.id, + supportedChainIds: [ton.id], + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + }); + await waitForWalletConnection(tonWallet); + + const tonRes = await tonWallet.result.current.signTransaction({ + network: "ton", + tx: JSON.stringify(rawTx), + txMeta: createTonTxMeta(), + ledgerHwAppId: null, + }); + + expect(tonRes).toEqual({ + signedTx: "hash", + broadcasted: true, + }); + expect(sendTransactionSpy).toHaveBeenCalledWith( + { + type: "ton", + tx: rawTx, + }, + createTonTxMeta() + ); + }); +}); diff --git a/packages/widget/tests/use-cases/sk-wallet.test.tsx b/packages/widget/tests/use-cases/sk-wallet.test.tsx deleted file mode 100644 index 4f4d2fb42..000000000 --- a/packages/widget/tests/use-cases/sk-wallet.test.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import { - Address, - beginCell, - type CommonMessageInfoRelaxedInternal, - internal, - storeMessageRelaxed, -} from "@ton/core"; -import { HttpResponse, http } from "msw"; -import { solana, ton } from "../../src/domain/types/chains/misc"; -import { MiscNetworks } from "../../src/domain/types/chains/networks"; -import type { SKExternalProviders } from "../../src/domain/types/wallets"; -import type { SKTxMeta } from "../../src/domain/types/wallets/generic-wallet"; -import { SKApiClientProvider } from "../../src/providers/api/api-client-provider"; -import { SKQueryClientProvider } from "../../src/providers/query-client"; -import { SettingsContextProvider } from "../../src/providers/settings"; -import { SKWalletProvider, useSKWallet } from "../../src/providers/sk-wallet"; -import { SendTransactionError } from "../../src/providers/sk-wallet/errors"; -import { SolanaProvider } from "../../src/providers/solana"; -import { TrackingContextProviderWithProps } from "../../src/providers/tracking"; -import { WagmiConfigProvider } from "../../src/providers/wagmi/provider"; -import { legacyApiRoute } from "../mocks/api-routes"; -import { mockDelay } from "../mocks/delay"; -import { describe, expect, it, vi } from "../utils/test-extend"; -import { renderHook } from "../utils/test-utils"; - -const renderHookWithExternalProvider = ( - externalProviders: SKExternalProviders, - options: { - variant?: "default" | "utila"; - } = {} -) => - renderHook(useSKWallet, { - wrapper: ({ children }) => ( - - - - - - - {children} - - - - - - - ), - }); - -const createSolanaTxMeta = (): SKTxMeta => ({ - txId: "", - actionId: "", - actionType: "STAKE", - txType: "APPROVAL", - amount: "100", - inputToken: { - address: "", - decimals: 0, - symbol: "", - name: "", - network: "solana", - }, - structuredTransaction: null, - annotatedTransaction: null, - providersDetails: [], -}); - -const createTonTxMeta = (): SKTxMeta => ({ - txId: "", - actionId: "", - actionType: "STAKE", - txType: "APPROVAL", - amount: "100", - inputToken: { - address: "", - decimals: 0, - symbol: "", - name: "", - network: "ton", - }, - structuredTransaction: null, - annotatedTransaction: null, - providersDetails: [], -}); - -const createDefaultTonTransactionFixture = () => { - const message = internal({ - to: Address.parseRaw( - "0:0000000000000000000000000000000000000000000000000000000000000000" - ), - value: 123n, - body: "Deposit", - }); - const info = message.info as CommonMessageInfoRelaxedInternal; - - return { - tx: JSON.stringify({ - seqno: 0, - message: beginCell() - .store(storeMessageRelaxed(message)) - .endCell() - .toBoc() - .toString("base64"), - }), - rawTx: [ - { - address: info.dest.toString(), - amount: info.value.coins.toString(), - payload: message.body.toBoc().toString("base64"), - }, - ], - }; -}; - -describe("SK Wallet", () => { - it("should work with solana external provider", async ({ worker }) => { - const switchChainSpy = vi.fn(async (_: number) => {}); - const sendTransactionSpy = vi.fn(async () => "hash"); - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - return HttpResponse.json([MiscNetworks.Solana]); - }) - ); - - const solanaWallet = await renderHookWithExternalProvider({ - type: "generic", - currentAddress: "9TCnDo7Txc5bC9SnE9iKsU5CyffLfeK4nrv1BFUmxkiJ", - currentChain: solana.id, - supportedChainIds: [solana.id], - provider: { - signMessage: async () => "hash", - switchChain: switchChainSpy, - sendTransaction: sendTransactionSpy, - }, - }); - - await expect.poll(() => solanaWallet.result.current.isConnected).toBe(true); - - const solanaRes = await solanaWallet.result.current.signTransaction({ - network: "solana", - tx: "AQIDBA==", - txMeta: createSolanaTxMeta(), - ledgerHwAppId: null, - }); - - expect(solanaRes.extract()).toEqual({ - signedTx: "hash", - broadcasted: true, - }); - expect(sendTransactionSpy).toHaveBeenCalledWith( - { - type: "solana", - tx: "01020304", - }, - createSolanaTxMeta() - ); - }); - - it("keeps hex solana external provider transactions in hex form", async ({ - worker, - }) => { - const switchChainSpy = vi.fn(async (_: number) => {}); - const sendTransactionSpy = vi.fn(async () => "hash"); - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - return HttpResponse.json([MiscNetworks.Solana]); - }) - ); - - const solanaWallet = await renderHookWithExternalProvider({ - type: "generic", - currentAddress: "9TCnDo7Txc5bC9SnE9iKsU5CyffLfeK4nrv1BFUmxkiJ", - currentChain: solana.id, - supportedChainIds: [solana.id], - provider: { - signMessage: async () => "hash", - switchChain: switchChainSpy, - sendTransaction: sendTransactionSpy, - }, - }); - - await expect.poll(() => solanaWallet.result.current.isConnected).toBe(true); - - const solanaRes = await solanaWallet.result.current.signTransaction({ - network: "solana", - tx: "0xA1B2", - txMeta: createSolanaTxMeta(), - ledgerHwAppId: null, - }); - - expect(solanaRes.extract()).toEqual({ - signedTx: "hash", - broadcasted: true, - }); - expect(sendTransactionSpy).toHaveBeenCalledWith( - { - type: "solana", - tx: "a1b2", - }, - createSolanaTxMeta() - ); - }); - - it("preserves custom external provider transaction errors", async ({ - worker, - }) => { - const customMessage = "Transaction blocked by policy"; - const switchChainSpy = vi.fn(async (_: number) => {}); - const sendTransactionSpy = vi.fn(async () => ({ - type: "error" as const, - error: customMessage, - })); - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - return HttpResponse.json([MiscNetworks.Solana]); - }) - ); - - const solanaWallet = await renderHookWithExternalProvider({ - type: "generic", - currentAddress: "9TCnDo7Txc5bC9SnE9iKsU5CyffLfeK4nrv1BFUmxkiJ", - currentChain: solana.id, - supportedChainIds: [solana.id], - provider: { - signMessage: async () => "hash", - switchChain: switchChainSpy, - sendTransaction: sendTransactionSpy, - }, - }); - - await expect.poll(() => solanaWallet.result.current.isConnected).toBe(true); - - const solanaRes = await solanaWallet.result.current.signTransaction({ - network: "solana", - tx: "12345", - txMeta: { - txId: "", - actionId: "", - actionType: "STAKE", - txType: "APPROVAL", - amount: "100", - inputToken: { - address: "", - decimals: 0, - symbol: "", - name: "", - network: "solana", - }, - structuredTransaction: null, - annotatedTransaction: null, - providersDetails: [], - }, - ledgerHwAppId: null, - }); - - expect(solanaRes.isLeft()).toBe(true); - - const error = solanaRes.extract() as SendTransactionError; - - expect(error).toBeInstanceOf(SendTransactionError); - expect(error.message).toBe("Send transaction failed"); - expect(error.customMessage).toBe(customMessage); - }); - - it("should work with ton external provider", async ({ worker }) => { - const switchChainSpy = vi.fn(async (_: number) => {}); - const sendTransactionSpy = vi.fn(async (_: unknown) => "hash"); - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - return HttpResponse.json([MiscNetworks.Ton]); - }) - ); - - const tonWallet = await renderHookWithExternalProvider({ - type: "generic", - currentAddress: "UQDyiNAyPy8QRQy45-SjxzrbKVOTOVyXaVGPZSLI9jxHF_Sy", - currentChain: ton.id, - supportedChainIds: [ton.id], - provider: { - signMessage: async () => "hash", - switchChain: switchChainSpy, - sendTransaction: sendTransactionSpy, - }, - }); - await expect - .poll( - () => - !tonWallet.result.current.isConnecting && - tonWallet.result.current.isConnected - ) - .toBe(true); - - const tonFixture = createDefaultTonTransactionFixture(); - const tonRes = await tonWallet.result.current.signTransaction({ - network: "ton", - tx: tonFixture.tx, - txMeta: createTonTxMeta(), - ledgerHwAppId: null, - }); - - expect(tonRes.extract()).toEqual({ - signedTx: "hash", - broadcasted: true, - }); - expect(sendTransactionSpy).toHaveBeenCalledWith( - { - type: "ton", - tx: tonFixture.rawTx, - }, - createTonTxMeta() - ); - }); - - it("keeps raw ton transactions unchanged for external provider", async ({ - worker, - }) => { - const switchChainSpy = vi.fn(async (_: number) => {}); - const sendTransactionSpy = vi.fn(async (_: unknown) => "hash"); - const rawTx = [ - { - address: "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", - amount: "123", - payload: "te6cckEBAQEAAgAAAA==", - }, - ]; - - worker.use( - http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { - await mockDelay(); - return HttpResponse.json([MiscNetworks.Ton]); - }) - ); - - const tonWallet = await renderHookWithExternalProvider({ - type: "generic", - currentAddress: "UQDyiNAyPy8QRQy45-SjxzrbKVOTOVyXaVGPZSLI9jxHF_Sy", - currentChain: ton.id, - supportedChainIds: [ton.id], - provider: { - signMessage: async () => "hash", - switchChain: switchChainSpy, - sendTransaction: sendTransactionSpy, - }, - }); - await expect.poll(() => tonWallet.result.current.isConnected).toBe(true); - - const tonRes = await tonWallet.result.current.signTransaction({ - network: "ton", - tx: JSON.stringify(rawTx), - txMeta: createTonTxMeta(), - ledgerHwAppId: null, - }); - - expect(tonRes.extract()).toEqual({ - signedTx: "hash", - broadcasted: true, - }); - expect(sendTransactionSpy).toHaveBeenCalledWith( - { - type: "ton", - tx: rawTx, - }, - createTonTxMeta() - ); - }); -}); diff --git a/packages/widget/tests/use-cases/solana-connector.test.ts b/packages/widget/tests/use-cases/solana-connector.test.ts index 0c9e7c61f..1161a9b57 100644 --- a/packages/widget/tests/use-cases/solana-connector.test.ts +++ b/packages/widget/tests/use-cases/solana-connector.test.ts @@ -1,15 +1,16 @@ -import type { Wallet } from "@solana/wallet-adapter-react"; import { type Connection, Transaction, VersionedTransaction, } from "@solana/web3.js"; +import { Array as EArray } from "effect"; import { afterEach, describe, expect, it, vi } from "vitest"; import { decodeSolanaTransactionToBuffer } from "../../src/domain/types/transaction"; import { deserializeSolanaTransaction, getSolanaConnectors, -} from "../../src/providers/misc/solana-connector"; +} from "../../src/services/wallet/connectors/misc/solana-connector"; +import type { SolanaWalletDescriptor } from "../../src/services/wallet/solana-runtime"; const createConnectorForTest = ({ sendTransaction = vi.fn(async () => "signed-hash"), @@ -29,14 +30,19 @@ const createConnectorForTest = ({ disconnect: vi.fn(), sendTransaction, }, - } as unknown as Wallet; - - const walletFactory = getSolanaConnectors({ - wallets: [wallet], - forceWalletConnectOnly: false, - connection, - variant: "default", - }).wallets[0]; + readyState: "Installed", + source: "fallback", + } as unknown as SolanaWalletDescriptor; + + const walletFactory = EArray.getUnsafe( + getSolanaConnectors({ + wallets: [wallet], + forceWalletConnectOnly: false, + connection, + variant: "default", + }).wallets, + 0 + ); const connectorFactory = walletFactory({} as never).createConnector( {} as never diff --git a/packages/widget/tests/use-cases/staking-flow/setup.ts b/packages/widget/tests/use-cases/staking-flow/setup.ts index bc893cde7..5ec6385b9 100644 --- a/packages/widget/tests/use-cases/staking-flow/setup.ts +++ b/packages/widget/tests/use-cases/staking-flow/setup.ts @@ -1,26 +1,31 @@ +import { Array as EArray, Schema } from "effect"; import { HttpResponse, http } from "msw"; import { avalanche } from "viem/chains"; import { vitest } from "vitest"; -import type { YieldCreateActionDto } from "../../../src/domain/types/action"; -import type { - ActionDto, - AddressesDto, - TokenDto, - TransactionDto, - YieldDto, -} from "../../../src/generated/api/legacy"; -import { waitForMs } from "../../../src/utils"; +import type { ActionCommand } from "../../../src/domain/schema/action-models"; +import { EarnYieldWithProvider } from "../../../src/domain/schema/earn-models"; +import type { LegacyTransaction } from "../../../src/domain/schema/legacy-models"; import { - yieldApiActionFixture, + yieldApiActionDtoFixture, yieldApiProviderFixture, + yieldApiTransactionDtoFixture, yieldApiTransactionFixture, yieldApiValidatorsFixture, - yieldApiYieldFixture, + yieldApiYieldDtoFixture, } from "../../fixtures"; +import type { + ActionDto, + AddressesDto, + TokenDto, + YieldDto, +} from "../../generated/legacy-api-types"; import { legacyApiRoute, yieldApiRoute } from "../../mocks/api-routes"; import { mockDelay } from "../../mocks/delay"; import { rkMockWallet } from "../../utils/mock-connector"; import type { TestWorker } from "../../utils/test-extend"; +import { waitForMs } from "../../utils/wait"; + +type TransactionDto = typeof LegacyTransaction.Encoded; export const setup = async (worker: TestWorker) => { const token: TokenDto = { @@ -91,7 +96,7 @@ export const setup = async (worker: TestWorker) => { name: "Benqi", description: "", externalLink: "https://benqi.fi/", - logoURI: "https://assets.stakek.it/providers/benqi.svg", + logoURI: "https://assets.stakek.it/app/composition/providers/benqi.svg", }, revshare: { enabled: true, @@ -129,8 +134,8 @@ export const setup = async (worker: TestWorker) => { validators: [], isAvailable: true, }; - const yieldApiYieldBase = yieldApiYieldFixture(); - const yieldApiYieldOp = yieldApiYieldFixture({ + const yieldApiYieldBase = yieldApiYieldDtoFixture(); + const yieldApiYieldOp = yieldApiYieldDtoFixture({ id: yieldOp.id, network: token.network, providerId: yieldOp.metadata.provider?.id ?? "benqi", @@ -333,10 +338,10 @@ export const setup = async (worker: TestWorker) => { http.post(yieldApiRoute("/v1/actions/enter"), async (info) => { await mockDelay(); - const body = (await info.request.json()) as YieldCreateActionDto; + const body = (await info.request.json()) as ActionCommand; return HttpResponse.json( - yieldApiActionFixture({ + yieldApiActionDtoFixture({ id: enterAction.id, yieldId: enterAction.integrationId, type: enterAction.type, @@ -345,8 +350,8 @@ export const setup = async (worker: TestWorker) => { amountRaw: body.arguments?.amount ?? null, amountUsd: null, transactions: [ - yieldApiTransactionFixture({ - id: enterAction.transactions[0].id, + yieldApiTransactionDtoFixture({ + id: EArray.getUnsafe(enterAction.transactions, 0).id, network: transactionConstruct.network, status: "CREATED", type: "STAKE", @@ -429,7 +434,7 @@ export const setup = async (worker: TestWorker) => { ...yieldApiYieldOp, provider: yieldApiProviderFixture({ id: "benqi", - logoURI: "https://assets.stakek.it/providers/benqi.svg", + logoURI: "https://assets.stakek.it/app/composition/providers/benqi.svg", name: "Benqi", website: "https://benqi.fi/", }), @@ -437,7 +442,7 @@ export const setup = async (worker: TestWorker) => { return { customConnectors, - yieldOp: mergedYieldOp, + yieldOp: Schema.decodeUnknownSync(EarnYieldWithProvider)(mergedYieldOp), enterAction, transactionConstruct, account, diff --git a/packages/widget/tests/use-cases/staking-flow/staking-flow.browser.test.tsx b/packages/widget/tests/use-cases/staking-flow/staking-flow.browser.test.tsx new file mode 100644 index 000000000..87f6fc5ad --- /dev/null +++ b/packages/widget/tests/use-cases/staking-flow/staking-flow.browser.test.tsx @@ -0,0 +1,178 @@ +import BigNumber from "bignumber.js"; +import { Array as EArray } from "effect"; +import { userEvent } from "vitest/browser"; +import { getYieldSummaryRewardToken } from "../../../src/features/yield-summary"; +import { formatAddress } from "../../../src/shared/lib/general"; +import { formatNumber } from "../../../src/shared/lib/number-format"; +import { describe, expect, it } from "../../utils/test-extend"; +import { renderApp } from "../../utils/test-utils"; +import { setup } from "./setup"; + +describe("Staking flow", () => { + it("Works as expected", async ({ worker }) => { + const { + customConnectors, + yieldOp, + transactionConstruct, + account, + requestFn, + } = await setup(worker); + + const app = await renderApp({ + wagmi: { + __customConnectors__: customConnectors, + forceWalletConnectOnly: false, + }, + }); + + await expect + .element(app.getByText(formatAddress(account))) + .toBeInTheDocument(); + + await app.getByTestId("select-opportunity").click(); + + const selectContainer = app.getByTestId("select-modal__container"); + + await selectContainer + .getByTestId(/^select-opportunity__item_avalanche-avax-liquid-staking/) + .click(); + + await expect + .poll( + () => + app + .getByTestId("select-opportunity") + .getByText(yieldOp.outputToken?.symbol ?? yieldOp.token.symbol) + .length + ) + .greaterThan(0); + + const stakeAmount = "0.1"; + + await userEvent.click(app.getByTestId("number-input")); + await userEvent.keyboard(stakeAmount); + + await expect + .element(app.getByTestId("number-input")) + .toHaveValue(stakeAmount); + + await expect.element(app.getByText("Stake").first()).toBeInTheDocument(); + + await expect + .element(app.getByTestId("estimated-reward__percent").getByText("5.08%")) + .toBeInTheDocument(); + + await expect + .element( + app + .getByTestId("estimated-reward__yearly") + .getByText(`0.00508 ${yieldOp.token.symbol}`) + ) + .toBeInTheDocument(); + + await expect + .element( + app + .getByTestId("estimated-reward__monthly") + .getByText(`0.00042 ${yieldOp.token.symbol}`) + ) + .toBeInTheDocument(); + + const rewardTokenDetails = getYieldSummaryRewardToken(yieldOp)!; + + await expect + .element(app.getByText(`You'll receive`).first()) + .toBeInTheDocument(); + await expect + .element( + app + .getByText( + `${EArray.getUnsafe(rewardTokenDetails.rewardTokens, 0).symbol}` + ) + .first() + ) + .toBeInTheDocument(); + + await expect.element(app.getByText("Stake").first()).toBeInTheDocument(); + + await userEvent.click(app.getByText("Stake").last()); + + const totalGasFee = new BigNumber( + transactionConstruct.gasEstimate?.amount ?? 0 + ); + + await expect + .element( + app + .getByTestId("estimated_gas_fee") + .getByText( + `${formatNumber(totalGasFee, 10)} ${yieldOp.token.symbol}`, + { exact: false } + ) + ) + .toBeInTheDocument(); + + await expect + .element(app.getByText(stakeAmount).first()) + .toBeInTheDocument(); + + await expect + .element(app.getByText(yieldOp.token.symbol).first()) + .toBeInTheDocument(); + await expect.element(app.getByText("& earn").first()).toBeInTheDocument(); + await expect.element(app.getByText("5.08%").first()).toBeInTheDocument(); + + await expect + .element( + app + .getByTestId("estimated-reward__yearly") + .getByText(`0.00508 ${yieldOp.token.symbol}`) + ) + .toBeInTheDocument(); + + await expect + .element( + app + .getByTestId("estimated-reward__monthly") + .getByText(`0.00042 ${yieldOp.token.symbol}`) + ) + .toBeInTheDocument(); + + const confirmButton = app.getByRole("button", { + exact: true, + name: "Confirm", + }); + + await expect.element(confirmButton).toBeInTheDocument(); + + await userEvent.click(confirmButton); + + await expect.element(app.getByText("Follow Steps")).toBeInTheDocument(); + + await expect + .poll(() => requestFn, { timeout: 1000 * 5 }) + .toHaveBeenCalledWith( + { + method: "eth_sendTransaction", + params: expect.anything(), + }, + undefined + ); + + await expect + .poll(() => requestFn, { timeout: 1000 * 5 }) + .toHaveBeenCalledWith({ method: "eth_chainId" }, undefined); + + await expect + .element( + app.getByText( + `Successfully staked ${stakeAmount} ${yieldOp.token.symbol}` + ) + ) + .toBeInTheDocument(); + await expect + .element(app.getByText("View Stake transaction")) + .toBeInTheDocument(); + app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/staking-flow/staking-flow.test.tsx b/packages/widget/tests/use-cases/staking-flow/staking-flow.test.tsx deleted file mode 100644 index 749d45b02..000000000 --- a/packages/widget/tests/use-cases/staking-flow/staking-flow.test.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import BigNumber from "bignumber.js"; -import { Just } from "purify-ts"; -import { userEvent } from "vitest/browser"; -import { useRewardTokenDetails } from "../../../src/hooks/use-reward-token-details"; -import { formatAddress, formatNumber } from "../../../src/utils"; -import { describe, expect, it } from "../../utils/test-extend"; -import { renderApp, renderHook } from "../../utils/test-utils"; -import { setup } from "./setup"; - -describe("Staking flow", () => { - it("Works as expected", async ({ worker }) => { - const { - customConnectors, - yieldOp, - transactionConstruct, - account, - requestFn, - } = await setup(worker); - - const app = await renderApp({ - wagmi: { - __customConnectors__: customConnectors, - forceWalletConnectOnly: false, - }, - }); - - await expect - .element(app.getByText(formatAddress(account))) - .toBeInTheDocument(); - - await app.getByTestId("select-opportunity").click(); - - const selectContainer = app.getByTestId("select-modal__container"); - - await selectContainer - .getByTestId(/^select-opportunity__item_avalanche-avax-liquid-staking/) - .click(); - - await expect - .poll( - () => - app - .getByTestId("select-opportunity") - .getByText(yieldOp.outputToken?.symbol ?? yieldOp.token.symbol) - .length - ) - .greaterThan(0); - - const stakeAmount = "0.1"; - - await userEvent.click(app.getByTestId("number-input")); - await userEvent.keyboard(stakeAmount); - - await expect - .element(app.getByTestId("number-input")) - .toHaveValue(stakeAmount); - - await expect.element(app.getByText("Stake").first()).toBeInTheDocument(); - - await expect - .element(app.getByTestId("estimated-reward__percent").getByText("5.08%")) - .toBeInTheDocument(); - - await expect - .element( - app - .getByTestId("estimated-reward__yearly") - .getByText(`0.00508 ${yieldOp.token.symbol}`) - ) - .toBeInTheDocument(); - - await expect - .element( - app - .getByTestId("estimated-reward__monthly") - .getByText(`0.00042 ${yieldOp.token.symbol}`) - ) - .toBeInTheDocument(); - - const rewardTokenDetails = ( - await renderHook(() => - useRewardTokenDetails( - Just(yieldOp) as unknown as Parameters< - typeof useRewardTokenDetails - >[0] - ) - ) - ).result.current.unsafeCoerce(); - - await expect - .element(app.getByText(`You'll receive`).first()) - .toBeInTheDocument(); - await expect - .element( - app.getByText(`${rewardTokenDetails.rewardTokens[0].symbol}`).first() - ) - .toBeInTheDocument(); - - await expect.element(app.getByText("Stake").first()).toBeInTheDocument(); - - await userEvent.click(app.getByText("Stake").last()); - - const totalGasFee = new BigNumber( - transactionConstruct.gasEstimate?.amount ?? 0 - ); - - await expect - .element( - app - .getByTestId("estimated_gas_fee") - .getByText( - `${formatNumber(totalGasFee, 10)} ${yieldOp.token.symbol}`, - { exact: false } - ) - ) - .toBeInTheDocument(); - - await expect - .element(app.getByText(stakeAmount).first()) - .toBeInTheDocument(); - - await expect - .element(app.getByText(yieldOp.token.symbol).first()) - .toBeInTheDocument(); - await expect.element(app.getByText("& earn").first()).toBeInTheDocument(); - await expect.element(app.getByText("5.08%").first()).toBeInTheDocument(); - - await expect - .element( - app - .getByTestId("estimated-reward__yearly") - .getByText(`0.00508 ${yieldOp.token.symbol}`) - ) - .toBeInTheDocument(); - - await expect - .element( - app - .getByTestId("estimated-reward__monthly") - .getByText(`0.00042 ${yieldOp.token.symbol}`) - ) - .toBeInTheDocument(); - - const confirmButton = app.getByRole("button", { - exact: true, - name: "Confirm", - }); - - await expect.element(confirmButton).toBeInTheDocument(); - - await userEvent.click(confirmButton); - - await expect.element(app.getByText("Follow Steps")).toBeInTheDocument(); - - await expect - .poll(() => requestFn, { timeout: 1000 * 5 }) - .toHaveBeenCalledWith({ - method: "eth_sendTransaction", - params: expect.anything(), - }); - - await expect - .poll(() => requestFn, { timeout: 1000 * 5 }) - .toHaveBeenCalledWith({ method: "eth_chainId" }); - - await expect - .element( - app.getByText( - `Successfully staked ${stakeAmount} ${yieldOp.token.symbol}` - ) - ) - .toBeInTheDocument(); - await expect - .element(app.getByText("View Stake transaction")) - .toBeInTheDocument(); - app.unmount(); - }); -}); diff --git a/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts b/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts index f7691ab79..f45b4ab21 100644 --- a/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts +++ b/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts @@ -1,13 +1,15 @@ +import { Schema } from "effect"; import { HttpResponse, http } from "msw"; import { avalanche } from "viem/chains"; import { vitest } from "vitest"; -import type { YieldBalanceDto } from "../../../src/domain/types/positions"; +import { + EarnToken, + type EarnYield, +} from "../../../src/domain/schema/earn-models"; import type { YieldRewardRateDto } from "../../../src/domain/types/reward-rate"; -import type { Yield } from "../../../src/domain/types/yields"; -import { waitForMs } from "../../../src/utils"; import { legacyYieldFixture, - yieldApiYieldFixture, + yieldApiYieldDtoFixture, yieldBalanceFixture, yieldRewardRateFixture, } from "../../fixtures"; @@ -15,9 +17,10 @@ import { legacyApiRoute, yieldApiRoute } from "../../mocks/api-routes"; import { mockDelay } from "../../mocks/delay"; import { rkMockWallet } from "../../utils/mock-connector"; import type { TestWorker } from "../../utils/test-extend"; +import { waitForMs } from "../../utils/wait"; type LegacyTokenDto = ReturnType["token"]; -type YieldApiYieldDto = Omit; +type YieldApiYieldDto = typeof EarnYield.Encoded; const setUrl = ({ accountId, @@ -68,23 +71,23 @@ export const setup = async ( logoURI: "https://assets.stakek.it/tokens/usda.svg", }; - const rewardToken: LegacyTokenDto = { + const rewardToken = Schema.decodeUnknownSync(EarnToken)({ name: "United Stables", symbol: "U", decimals: 18, network: token.network, address: "0x58D97B57BB95320F9a05dC918Aef65434969c2B2", logoURI: "https://assets.stakek.it/tokens/usda.svg", - }; + }); - const morphoToken: LegacyTokenDto = { + const morphoToken = Schema.decodeUnknownSync(EarnToken)({ name: "Morpho Token", symbol: "MORPHO", decimals: 18, network: token.network, address: "0x58D97B57BB95320F9a05dC918Aef65434969c2B3", logoURI: "https://assets.stakek.it/tokens/usda.svg", - }; + }); const discoveryRewardRate: YieldRewardRateDto = yieldRewardRateFixture({ total: 0.045507546653006034, @@ -175,7 +178,7 @@ export const setup = async ( "avalanche-c-usda-trust-0xbeefa1abfebe621df50ceaef9f54fdb73648c92c-vault"; const legacyYieldBase = legacyYieldFixture(); - const rawYieldBase = yieldApiYieldFixture(); + const rawYieldBase = yieldApiYieldDtoFixture(); const legacyYield: ReturnType = { ...legacyYieldBase, @@ -217,7 +220,7 @@ export const setup = async ( name: "Trust", description: "", externalLink: "https://trustwallet.com", - logoURI: "https://assets.stakek.it/providers/benqi.svg", + logoURI: "https://assets.stakek.it/app/composition/providers/benqi.svg", }, }, status: { @@ -245,7 +248,7 @@ export const setup = async ( ...(rawYieldBase.metadata ?? {}), name: "Trust USDA Earn", description: "Trust campaign vault", - logoURI: "https://assets.stakek.it/providers/benqi.svg", + logoURI: "https://assets.stakek.it/app/composition/providers/benqi.svg", }, mechanics: { ...(rawYieldBase.mechanics ?? {}), @@ -262,7 +265,7 @@ export const setup = async ( }, }; - const activeBalance: YieldBalanceDto = yieldBalanceFixture({ + const activeBalance = yieldBalanceFixture({ address: account, type: "active", amount: "1000251.8279906842", diff --git a/packages/widget/tests/use-cases/trust-incentive-apy/trust-incentive-apy.test.tsx b/packages/widget/tests/use-cases/trust-incentive-apy/trust-incentive-apy.browser.test.tsx similarity index 100% rename from packages/widget/tests/use-cases/trust-incentive-apy/trust-incentive-apy.test.tsx rename to packages/widget/tests/use-cases/trust-incentive-apy/trust-incentive-apy.browser.test.tsx diff --git a/packages/widget/tests/use-cases/under-maintenance.browser.test.tsx b/packages/widget/tests/use-cases/under-maintenance.browser.test.tsx new file mode 100644 index 000000000..ff54c4b39 --- /dev/null +++ b/packages/widget/tests/use-cases/under-maintenance.browser.test.tsx @@ -0,0 +1,25 @@ +import { HttpResponse, http } from "msw"; +import { yieldApiRoute } from "../mocks/api-routes"; +import { describe, expect, it } from "../utils/test-extend"; +import { renderApp } from "../utils/test-utils"; + +describe("Under maintenance", () => { + it("Show under maintenance popup", async ({ worker }) => { + worker.use( + http.get(yieldApiRoute("/health"), async () => { + return HttpResponse.json({ + status: "FAIL", + timestamp: "2026-07-23T00:00:00.000Z", + }); + }) + ); + + const app = await renderApp(); + + await expect + .element(app.getByTestId("under-maintenance")) + .toBeInTheDocument(); + + app.unmount(); + }); +}); diff --git a/packages/widget/tests/use-cases/under-maintenance.test.tsx b/packages/widget/tests/use-cases/under-maintenance.test.tsx deleted file mode 100644 index 67d450cf2..000000000 --- a/packages/widget/tests/use-cases/under-maintenance.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { HttpResponse, http } from "msw"; -import { yieldApiRoute } from "../mocks/api-routes"; -import { describe, expect, it } from "../utils/test-extend"; -import { renderApp } from "../utils/test-utils"; - -describe("Under maintenance", () => { - it("Show under maintenance popup", async ({ worker }) => { - worker.use( - http.get(yieldApiRoute("/health"), async () => { - return HttpResponse.json({ - status: "FAIL", - timestamp: new Date().toISOString(), - }); - }) - ); - - const app = await renderApp(); - - await expect - .element(app.getByTestId("under-maintenance")) - .toBeInTheDocument(); - - app.unmount(); - }); -}); diff --git a/packages/widget/tests/utils/atom-runtime-provider.tsx b/packages/widget/tests/utils/atom-runtime-provider.tsx new file mode 100644 index 000000000..5672bbd10 --- /dev/null +++ b/packages/widget/tests/utils/atom-runtime-provider.tsx @@ -0,0 +1,15 @@ +import type { PropsWithChildren } from "react"; +import { SKAtomRegistryProvider } from "../../src/app/composition/providers/atom-runtime"; +import { applicationRoutes } from "../../src/app/routes/application-routes"; +import type { WidgetConfig } from "../../src/services/config/widget-config"; + +export const TestAtomRuntimeProvider = ({ + children, + settings, +}: PropsWithChildren<{ + readonly settings: WidgetConfig; +}>) => ( + + {children} + +); diff --git a/packages/widget/tests/utils/mock-connector.ts b/packages/widget/tests/utils/mock-connector.ts index a88901eae..034cd6826 100644 --- a/packages/widget/tests/utils/mock-connector.ts +++ b/packages/widget/tests/utils/mock-connector.ts @@ -1,9 +1,10 @@ +import type { WalletList } from "@stakekit/rainbowkit"; import { type EIP1193Provider, numberToHex, SwitchChainError } from "viem"; import type { CreateConnectorFn } from "wagmi"; import { ChainNotConfiguredError, custom } from "wagmi"; +import type { Chain } from "wagmi/chains"; import type { MockParameters } from "wagmi/connectors"; import { mock as mockConnector } from "wagmi/connectors"; -import type { BuildWagmiConfig } from "../../src/providers/wagmi"; interface MyWalletOptions { accounts: MockParameters["accounts"]; @@ -16,7 +17,7 @@ export const rkMockWallet = connectorParams, accounts, requestFn, - }: MyWalletOptions): Parameters[0]["customConnectors"] => + }: MyWalletOptions): ((chains: Chain[]) => WalletList) => () => [ { groupName: "Mock Wallet", diff --git a/packages/widget/tests/utils/setup.browser.ts b/packages/widget/tests/utils/setup.browser.ts new file mode 100644 index 000000000..10cd7e37e --- /dev/null +++ b/packages/widget/tests/utils/setup.browser.ts @@ -0,0 +1,40 @@ +import { MotionGlobalConfig } from "motion/react"; +import { vi } from "vitest"; + +MotionGlobalConfig.skipAnimations = true; + +const ignoredConsoleMessages = [ + "All fibers interrupted without error", + "Lit is in dev mode", +]; + +const getConsoleMessage = (arg: unknown) => { + if (arg instanceof Error) { + return `${arg.name}: ${arg.message}`; + } + + return typeof arg === "string" ? arg : ""; +}; + +const isIgnoredConsoleNoise = (args: ReadonlyArray) => + args.some((arg) => { + const message = getConsoleMessage(arg); + + return ignoredConsoleMessages.some((ignored) => message.includes(ignored)); + }); + +const silenceConsoleNoise = (method: "error" | "log" | "warn") => { + const original = console[method].bind(console); + + vi.spyOn(console, method).mockImplementation((...args: unknown[]) => { + if (isIgnoredConsoleNoise(args)) { + return; + } + + original(...args); + }); +}; + +silenceConsoleNoise("error"); +silenceConsoleNoise("log"); +silenceConsoleNoise("warn"); diff --git a/packages/widget/tests/utils/setup.dom.ts b/packages/widget/tests/utils/setup.dom.ts new file mode 100644 index 000000000..a7a8afbc7 --- /dev/null +++ b/packages/widget/tests/utils/setup.dom.ts @@ -0,0 +1,5 @@ +import { MotionGlobalConfig } from "motion/react"; + +MotionGlobalConfig.skipAnimations = true; + +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); diff --git a/packages/widget/tests/utils/setup.ts b/packages/widget/tests/utils/setup.ts deleted file mode 100644 index eddc8a736..000000000 --- a/packages/widget/tests/utils/setup.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { MotionGlobalConfig } from "motion/react"; -import { vi } from "vitest"; - -MotionGlobalConfig.skipAnimations = true; - -vi.setConfig({ testTimeout: 60000 }); - -const ignoredConsoleMessages = [ - "All fibers interrupted without error", - "Lit is in dev mode", -]; - -const getConsoleMessage = (arg: unknown) => { - if (arg instanceof Error) { - return `${arg.name}: ${arg.message}`; - } - - return typeof arg === "string" ? arg : ""; -}; - -const isIgnoredConsoleNoise = (args: ReadonlyArray) => - args.some((arg) => { - const message = getConsoleMessage(arg); - - return ignoredConsoleMessages.some((ignored) => message.includes(ignored)); - }); - -const silenceConsoleNoise = (method: "error" | "log" | "warn") => { - const original = console[method].bind(console); - - vi.spyOn(console, method).mockImplementation((...args: unknown[]) => { - if (isIgnoredConsoleNoise(args)) { - return; - } - - original(...args); - }); -}; - -silenceConsoleNoise("error"); -silenceConsoleNoise("log"); -silenceConsoleNoise("warn"); diff --git a/packages/widget/tests/utils/stakekit-api-layer.ts b/packages/widget/tests/utils/stakekit-api-layer.ts new file mode 100644 index 000000000..9e0d11602 --- /dev/null +++ b/packages/widget/tests/utils/stakekit-api-layer.ts @@ -0,0 +1,44 @@ +import { Effect, Layer, Stream } from "effect"; +import { normalizeWidgetConfig } from "../../src/app/config/settings"; +import { BorrowOperations } from "../../src/services/api/borrow-operations"; +import { BorrowResourceSource } from "../../src/services/api/borrow-resource-source"; +import { LegacyResourceSource } from "../../src/services/api/legacy-resource-source"; +import { ApiTransportService } from "../../src/services/api/transport"; +import { YieldOperations } from "../../src/services/api/yield-operations"; +import { YieldResourceSource } from "../../src/services/api/yield-resource-source"; +import { + type WidgetApiConfig, + WidgetConfigService, +} from "../../src/services/config/widget-config"; +import { RichErrorService } from "../../src/services/errors/rich-error-service"; + +const makeTestLayers = (api: WidgetApiConfig) => { + const config = normalizeWidgetConfig({ ...api, variant: "default" }); + const configLayer = WidgetConfigService.layer({ + initial: config, + changes: Stream.never, + current: Effect.succeed(config), + }); + const richErrorLayer = RichErrorService.layer.pipe( + Layer.provide(configLayer) + ); + const transportLayer = ApiTransportService.layer.pipe( + Layer.provide(richErrorLayer), + Layer.provide(configLayer) + ); + const apiLayer = Layer.mergeAll( + BorrowOperations.layer, + BorrowResourceSource.layer, + LegacyResourceSource.layer, + YieldOperations.layer, + YieldResourceSource.layer + ).pipe(Layer.provide(transportLayer)); + + return { apiLayer, richErrorLayer } as const; +}; + +export const makeTestStakeKitApiLayer = (api: WidgetApiConfig) => { + const { apiLayer, richErrorLayer } = makeTestLayers(api); + + return Layer.merge(apiLayer, richErrorLayer).pipe(Layer.fresh); +}; diff --git a/packages/widget/tests/utils/test-extend.dom.ts b/packages/widget/tests/utils/test-extend.dom.ts new file mode 100644 index 000000000..3497d9adc --- /dev/null +++ b/packages/widget/tests/utils/test-extend.dom.ts @@ -0,0 +1,34 @@ +import { afterAll, test as base, describe, expect, vi } from "vitest"; +import { server } from "../mocks/server"; + +let isServerStarted = false; + +const test = base.extend<{ worker: typeof server }>({ + worker: [ + // biome-ignore lint/correctness/noEmptyPattern: Vitest fixtures require object destructuring here. + async ({}, use) => { + if (!isServerStarted) { + server.listen({ onUnhandledRequest: "error" }); + isServerStarted = true; + } + + await use(server); + + server.resetHandlers(); + }, + { + auto: true, + }, + ], +}); + +afterAll(() => { + if (isServerStarted) { + server.close(); + isServerStarted = false; + } +}); + +export const it = test; + +export { describe, expect, vi }; diff --git a/packages/widget/tests/utils/test-utils.dom.tsx b/packages/widget/tests/utils/test-utils.dom.tsx new file mode 100644 index 000000000..a198a0280 --- /dev/null +++ b/packages/widget/tests/utils/test-utils.dom.tsx @@ -0,0 +1,73 @@ +import { act, type ComponentType, createElement, type ReactNode } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach } from "vitest"; + +type Wrapper = ComponentType<{ children: ReactNode }>; + +const mountedRoots = new Set<{ container: HTMLElement; root: Root }>(); + +const cleanupRoot = (entry: { container: HTMLElement; root: Root }) => { + act(() => entry.root.unmount()); + entry.container.remove(); + mountedRoots.delete(entry); +}; + +export const render = async (ui: ReactNode) => { + const container = document.createElement("div"); + document.body.append(container); + + const root = createRoot(container); + const entry = { container, root }; + mountedRoots.add(entry); + + await act(async () => root.render(ui)); + + return { + container, + rerender: async (nextUi: ReactNode) => { + await act(async () => root.render(nextUi)); + }, + unmount: () => cleanupRoot(entry), + }; +}; + +export const renderHook = async ( + callback: (props: Props) => Result, + options: { + initialProps?: Props; + wrapper?: Wrapper; + } = {} +) => { + let current: Result; + let props = options.initialProps as Props; + + const TestComponent = () => { + current = callback(props); + return null; + }; + const getUi = () => { + const hook = createElement(TestComponent); + return options.wrapper ? createElement(options.wrapper, null, hook) : hook; + }; + const app = await render(getUi()); + + return { + act, + result: { + get current() { + return current; + }, + }, + rerender: async (nextProps: Props) => { + props = nextProps; + await app.rerender(getUi()); + }, + unmount: app.unmount, + }; +}; + +afterEach(() => { + for (const entry of [...mountedRoots]) { + cleanupRoot(entry); + } +}); diff --git a/packages/widget/tests/utils/test-utils.tsx b/packages/widget/tests/utils/test-utils.tsx index 7a6265e22..6055384d1 100644 --- a/packages/widget/tests/utils/test-utils.tsx +++ b/packages/widget/tests/utils/test-utils.tsx @@ -1,11 +1,11 @@ import type { ComponentProps } from "react"; import { type RenderOptions, render } from "vitest-browser-react"; import { SKApp } from "../../src/App"; -import type { SettingsContextProvider } from "../../src/providers/settings"; +import type { SettingsProps } from "../../src/public-api/types"; const renderApp = (opts?: { options?: RenderOptions; - wagmi?: ComponentProps["wagmi"]; + wagmi?: SettingsProps["wagmi"]; skProps?: ComponentProps; }) => { const App = ( diff --git a/packages/widget/tests/utils/validators.ts b/packages/widget/tests/utils/validators.ts new file mode 100644 index 000000000..cb1a0362d --- /dev/null +++ b/packages/widget/tests/utils/validators.ts @@ -0,0 +1,5 @@ +import { Schema } from "effect"; +import { EarnValidator } from "../../src/domain/schema/earn-models"; + +export const decodeValidator = (validator: typeof EarnValidator.Encoded) => + Schema.decodeUnknownSync(EarnValidator)(validator); diff --git a/packages/widget/tests/utils/wait.ts b/packages/widget/tests/utils/wait.ts new file mode 100644 index 000000000..23aad8314 --- /dev/null +++ b/packages/widget/tests/utils/wait.ts @@ -0,0 +1,2 @@ +export const waitForMs = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/packages/widget/tests/utils/wallet-operations.ts b/packages/widget/tests/utils/wallet-operations.ts new file mode 100644 index 000000000..bdff31db7 --- /dev/null +++ b/packages/widget/tests/utils/wallet-operations.ts @@ -0,0 +1,6 @@ +import type { WalletService } from "../../src/services/wallet/wallet-service"; + +export type WalletOperations = Omit< + WalletService["Service"], + "bind" | "persistPublicKey" +>; diff --git a/packages/widget/tests/utils/widget-config-provider.tsx b/packages/widget/tests/utils/widget-config-provider.tsx new file mode 100644 index 000000000..8205d94f5 --- /dev/null +++ b/packages/widget/tests/utils/widget-config-provider.tsx @@ -0,0 +1,20 @@ +import { RegistryProvider } from "@effect/atom-react"; +import type { PropsWithChildren } from "react"; +import { + normalizeWidgetConfig, + widgetConfigAtom, +} from "../../src/app/config/settings"; +import type { SettingsProps, VariantProps } from "../../src/public-api/types"; + +export const TestWidgetConfigProvider = ({ + children, + ...props +}: PropsWithChildren) => { + const settings = normalizeWidgetConfig(props); + + return ( + + {children} + + ); +}; diff --git a/packages/widget/tsconfig.build.json b/packages/widget/tsconfig.build.json index 6acb3a399..c782250e3 100644 --- a/packages/widget/tsconfig.build.json +++ b/packages/widget/tsconfig.build.json @@ -1,10 +1,10 @@ { "extends": "./tsconfig.json", - "include": ["src"], + "include": ["src/public-api"], "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "rootDir": "src", + "rootDir": "src/public-api", "outDir": "dist/types", "noEmit": false, "tsBuildInfoFile": "dist/.tsbuildinfo/tsconfig.build.tsbuildinfo" diff --git a/packages/widget/vite/vite.config.base.ts b/packages/widget/vite/vite.config.base.ts index 7e65c8be3..d066b2305 100644 --- a/packages/widget/vite/vite.config.base.ts +++ b/packages/widget/vite/vite.config.base.ts @@ -2,7 +2,6 @@ import path from "node:path"; import babel from "@rolldown/plugin-babel"; import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; import react, { reactCompilerPreset } from "@vitejs/plugin-react"; -import { playwright } from "@vitest/browser-playwright"; import autoprefixer from "autoprefixer"; import merge from "lodash.merge"; import macros from "unplugin-macros/vite"; @@ -21,6 +20,22 @@ declare module "vite" { } } +// @vanilla-extract/vite-plugin 5.2.4 declines virtual CSS modules whose +// source file only composes existing classes and therefore emits no new CSS. +// Preserve the plugin's pre-5.2.4 empty-module behavior as a post hook. +const vanillaExtractEmptyCssFallback: Plugin = { + name: "stakekit:vanilla-extract-empty-css-fallback", + enforce: "post", + resolveId(source) { + const [id] = source.split("?"); + if (id?.endsWith(".vanilla.css")) return source; + }, + load(source) { + const [id] = source.split("?"); + if (id?.endsWith(".vanilla.css")) return ""; + }, +}; + export const getConfig = ( overides?: Partial, options?: { plugins?: Plugin[] } @@ -38,28 +53,26 @@ export const getConfig = ( "vite-plugin-node-polyfills/shims/process", "@vanilla-extract/recipes/createRuntimeFn", "@vanilla-extract/sprinkles/createRuntimeSprinkles", - "date-fns/locale", + "react-router/dom", ], }, - test: { - browser: { - enabled: true, - screenshotFailures: false, - provider: playwright(), - instances: [{ browser: "chromium" }], - viewport: { width: 800, height: 900 }, - headless: true, - }, - include: ["tests/**/*.test.{ts,tsx}"], - setupFiles: [path.resolve(__dirname, "..", "tests/utils/setup.ts")], - }, plugins: [ ...(options?.plugins ?? []), nodePolyfills({ include: ["buffer", "crypto"] }), macros(), react(), - babel({ presets: [reactCompilerPreset()] }), + babel({ + presets: [reactCompilerPreset()], + // Skip large non-React modules; they trip Babel's 500KB styling note and gain nothing from React Compiler. + exclude: [ + /[/\\]node_modules[/\\]|^\0rolldown\/runtime\.js$/, + /[/\\]src[/\\]generated[/\\]/, + // Macro-inlined cosmos chain registry data. + /[/\\]connectors[/\\]cosmos[/\\]chains[/\\]chain-registry\.ts$/, + ], + }), vanillaExtractPlugin(), + vanillaExtractEmptyCssFallback, ], css: { postcss: { diff --git a/packages/widget/vite/vitest.config.ts b/packages/widget/vite/vitest.config.ts new file mode 100644 index 000000000..3850ee470 --- /dev/null +++ b/packages/widget/vite/vitest.config.ts @@ -0,0 +1,78 @@ +import path from "node:path"; +import { playwright } from "@vitest/browser-playwright"; +import { defineConfig } from "vite"; +import { getConfig } from "./vite.config.base"; + +const browserTestPattern = "tests/**/*.browser.test.{ts,tsx}"; +const domTestPattern = "tests/**/*.dom.test.{ts,tsx}"; +const unitTestPatterns = [ + "tests/**/*.test.{ts,tsx}", + "scripts/**/*.test.ts", +] as const; +const inlineTestDependencies = [ + /@ledgerhq/, + /@luno-kit/, + /@solana\/wallet-adapter/, + /@tronweb3/, + /@zondax/, +]; + +export default defineConfig( + getConfig({ + test: { + projects: [ + { + extends: true, + test: { + name: "unit", + environment: "node", + exclude: [browserTestPattern, domTestPattern], + include: [...unitTestPatterns], + testTimeout: 5_000, + server: { + deps: { + inline: inlineTestDependencies, + }, + }, + }, + }, + { + extends: true, + test: { + name: "dom", + environment: "jsdom", + include: [domTestPattern], + setupFiles: [ + path.resolve(__dirname, "..", "tests/utils/setup.dom.ts"), + ], + testTimeout: 10_000, + server: { + deps: { + inline: inlineTestDependencies, + }, + }, + }, + }, + { + extends: true, + test: { + name: "browser", + include: [browserTestPattern], + setupFiles: [ + path.resolve(__dirname, "..", "tests/utils/setup.browser.ts"), + ], + testTimeout: 20_000, + browser: { + enabled: true, + screenshotFailures: false, + provider: playwright(), + instances: [{ browser: "chromium" }], + viewport: { width: 800, height: 900 }, + headless: true, + }, + }, + }, + ], + }, + }) +); diff --git a/patches/purify-ts.patch b/patches/purify-ts.patch deleted file mode 100644 index c2906ba66..000000000 --- a/patches/purify-ts.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/Maybe.d.ts b/Maybe.d.ts -index f91d96c3a8420d0d9eedef3f2c06aaa750f69494..a4af8b3d668c77ebbe3ddad9ff8b1cb12a723d02 100644 ---- a/Maybe.d.ts -+++ b/Maybe.d.ts -@@ -5,7 +5,7 @@ export type MaybePatterns = { - } | { - _: () => U; - }; --interface AlwaysJust { -+export interface AlwaysJust { - kind: '$$MaybeAlwaysJust'; - } - type ExtractMaybe = T extends never ? TDefault : T | TDefault; -@@ -103,7 +103,7 @@ interface MaybeTypeRef { - } - export declare const Maybe: MaybeTypeRef; - declare class Nothing implements Maybe { -- private __value; -+ // private __value; - isJust(): this is AlwaysJust; - isNothing(): this is Nothing; - inspect(): string; -diff --git a/esm/Maybe.d.ts b/esm/Maybe.d.ts -index f91d96c3a8420d0d9eedef3f2c06aaa750f69494..a4af8b3d668c77ebbe3ddad9ff8b1cb12a723d02 100644 ---- a/esm/Maybe.d.ts -+++ b/esm/Maybe.d.ts -@@ -5,7 +5,7 @@ export type MaybePatterns = { - } | { - _: () => U; - }; --interface AlwaysJust { -+export interface AlwaysJust { - kind: '$$MaybeAlwaysJust'; - } - type ExtractMaybe = T extends never ? TDefault : T | TDefault; -@@ -103,7 +103,7 @@ interface MaybeTypeRef { - } - export declare const Maybe: MaybeTypeRef; - declare class Nothing implements Maybe { -- private __value; -+ // private __value; - isJust(): this is AlwaysJust; - isNothing(): this is Nothing; - inspect(): string; diff --git a/patches/vite-plugin-node-polyfills@0.26.0.patch b/patches/vite-plugin-node-polyfills@0.26.0.patch deleted file mode 100644 index 373e0b185..000000000 --- a/patches/vite-plugin-node-polyfills@0.26.0.patch +++ /dev/null @@ -1,63 +0,0 @@ -diff --git a/dist/index.cjs b/dist/index.cjs -index 835fc54b10d46f4f8e2153377c71f24f83eaa284..137ba1902b6129e93baee194f8bc0a92e3fdfb24 100644 ---- a/dist/index.cjs -+++ b/dist/index.cjs -@@ -1,3 +1,3 @@ - "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const T=require("node:module"),w=require("@rollup/plugin-inject"),x=require("node-stdlib-browser"),O=require("node-stdlib-browser/helpers/rollup/plugin"),P=require("node-stdlib-browser/helpers/esbuild/plugin");var h=typeof document<"u"?document.currentScript:null;const g=l=>l&&l.__esModule?l:{default:l},R=g(w),S=g(x),q=g(P),_=(l,e)=>b(l)===b(e),s=(l,e)=>l?l===!0?!0:l===e:!1,$=l=>l.startsWith("node:"),I=l=>{const e=l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`^${e}$`)},b=l=>l.replace(/^node:/,""),a={buffer:["import __buffer_polyfill from 'vite-plugin-node-polyfills/shims/buffer'","globalThis.Buffer = globalThis.Buffer || __buffer_polyfill"],global:["import __global_polyfill from 'vite-plugin-node-polyfills/shims/global'","globalThis.global = globalThis.global || __global_polyfill"],process:["import __process_polyfill from 'vite-plugin-node-polyfills/shims/process'","globalThis.process = globalThis.process || __process_polyfill"]},D=(l={})=>{const e={include:[],exclude:[],overrides:{},protocolImports:!0,...l,globals:{Buffer:!0,global:!0,process:!0,...l.globals}},y=o=>e.include.length>0?!e.include.some(r=>_(o,r)):e.exclude.some(r=>_(o,r)),B=o=>{if(s(e.globals.Buffer,"dev")&&/^buffer$/.test(o))return"vite-plugin-node-polyfills/shims/buffer";if(s(e.globals.global,"dev")&&/^global$/.test(o))return"vite-plugin-node-polyfills/shims/global";if(s(e.globals.process,"dev")&&/^process$/.test(o))return"vite-plugin-node-polyfills/shims/process";if(o in e.overrides)return e.overrides[o]},u=Object.entries(S.default).reduce((o,[r,i])=>(!e.protocolImports&&$(r)||y(r)||(o[r]=B(b(r))||i),o),{}),f=T.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:h&&h.src||new URL("index.cjs",document.baseURI).href),p=[...s(e.globals.Buffer,"dev")?[f.resolve("vite-plugin-node-polyfills/shims/buffer")]:[],...s(e.globals.global,"dev")?[f.resolve("vite-plugin-node-polyfills/shims/global")]:[],...s(e.globals.process,"dev")?[f.resolve("vite-plugin-node-polyfills/shims/process")]:[]],d=[...s(e.globals.Buffer,"dev")?a.buffer:[],...s(e.globals.global,"dev")?a.global:[],...s(e.globals.process,"dev")?a.process:[],""].join(` --`);return{name:"vite-plugin-node-polyfills",config(o,r){const i=r.command==="serve",v=!!this?.meta?.rolldownVersion,m={...i&&s(e.globals.Buffer,"dev")?{Buffer:"Buffer"}:{},...i&&s(e.globals.global,"dev")?{global:"global"}:{},...i&&s(e.globals.process,"dev")?{process:"process"}:{}},c={...s(e.globals.Buffer,"build")?{Buffer:"vite-plugin-node-polyfills/shims/buffer"}:{},...s(e.globals.global,"build")?{global:"vite-plugin-node-polyfills/shims/global"}:{},...s(e.globals.process,"build")?{process:"vite-plugin-node-polyfills/shims/process"}:{}};return{build:{rollupOptions:{onwarn:(t,n)=>{O.handleCircularDependancyWarning(t,()=>{if(o.build?.rollupOptions?.onwarn)return o.build.rollupOptions.onwarn(t,n);n(t)})},...Object.keys(c).length>0?v?{transform:{inject:c}}:{plugins:[R.default(c)]}:{}}},esbuild:{banner:i?d:void 0},optimizeDeps:{exclude:[...p],...v?{rolldownOptions:{resolve:{alias:{...u}},transform:{define:m},plugins:[{name:"vite-plugin-node-polyfills:optimizer",banner:i?d:void 0}]}}:{esbuildOptions:{banner:i?{js:d}:void 0,define:m,inject:[...p],plugins:[q.default(u),{name:"vite-plugin-node-polyfills-shims-resolver",setup(t){for(const n of p){const j=I(n);t.onResolve({filter:j},()=>({external:!1,path:n}))}}}]}}},resolve:{alias:{...u}}}}}};exports.nodePolyfills=D; -+`),F=o=>/\.(m?ts|[jt]sx)$/.test(o.split("?",1)[0]);let E=!1;return{name:"vite-plugin-node-polyfills",config(o,r){const i=r.command==="serve",v=!!this?.meta?.rolldownVersion,m={...i&&s(e.globals.Buffer,"dev")?{Buffer:"Buffer"}:{},...i&&s(e.globals.global,"dev")?{global:"global"}:{},...i&&s(e.globals.process,"dev")?{process:"process"}:{}},c={...s(e.globals.Buffer,"build")?{Buffer:"vite-plugin-node-polyfills/shims/buffer"}:{},...s(e.globals.global,"build")?{global:"vite-plugin-node-polyfills/shims/global"}:{},...s(e.globals.process,"build")?{process:"vite-plugin-node-polyfills/shims/process"}:{}};E=i;return{build:{rollupOptions:{onwarn:(t,n)=>{O.handleCircularDependancyWarning(t,()=>{if(o.build?.rollupOptions?.onwarn)return o.build.rollupOptions.onwarn(t,n);n(t)})},...Object.keys(c).length>0?v?{transform:{inject:c}}:{plugins:[R.default(c)]}:{}}},optimizeDeps:{exclude:[...p],...v?{rolldownOptions:{resolve:{alias:{...u}},transform:{define:m},plugins:[{name:"vite-plugin-node-polyfills:optimizer",banner:i?d:void 0}]}}:{esbuildOptions:{banner:i?{js:d}:void 0,define:m,inject:[...p],plugins:[q.default(u),{name:"vite-plugin-node-polyfills-shims-resolver",setup(t){for(const n of p){const j=I(n);t.onResolve({filter:j},()=>({external:!1,path:n}))}}}]}}},resolve:{alias:{...u}}}},transform(o,r){if(E&&d&&F(r))return{code:d+o,map:null}}}};exports.nodePolyfills=D; - //# sourceMappingURL=index.cjs.map -diff --git a/dist/index.js b/dist/index.js -index 007d9a62c8b5c9d4fbcfd25f4352e765bddd986e..93a8ec9cac91445b73433bcdf627edd6f92baa42 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -19,7 +19,7 @@ const v = (s, l) => d(s) === d(l), e = (s, l) => s ? s === !0 ? !0 : s === l : ! - "import __process_polyfill from 'vite-plugin-node-polyfills/shims/process'", - "globalThis.process = globalThis.process || __process_polyfill" - ] --}, D = (s = {}) => { -+}, E = (s) => /\.(m?ts|[jt]sx)$/.test(s.split("?", 1)[0]), D = (s = {}) => { - const l = { - include: [], - exclude: [], -@@ -52,6 +52,7 @@ const v = (s, l) => d(s) === d(l), e = (s, l) => s ? s === !0 ? !0 : s === l : ! - "" - ].join(` - `); -+ let _ = !1; - return { - name: "vite-plugin-node-polyfills", - config(o, r) { -@@ -65,6 +66,7 @@ const v = (s, l) => d(s) === d(l), e = (s, l) => s ? s === !0 ? !0 : s === l : ! - ...e(l.globals.global, "build") ? { global: "vite-plugin-node-polyfills/shims/global" } : {}, - ...e(l.globals.process, "build") ? { process: "vite-plugin-node-polyfills/shims/process" } : {} - }; -+ _ = i; - return { - build: { - rollupOptions: { -@@ -78,10 +80,6 @@ const v = (s, l) => d(s) === d(l), e = (s, l) => s ? s === !0 ? !0 : s === l : ! - ...Object.keys(b).length > 0 ? c ? { transform: { inject: b } } : { plugins: [x(b)] } : {} - } - }, -- esbuild: { -- // In dev, the global polyfills need to be injected as a banner in order for isolated scripts (such as Vue SFCs) to have access to them. -- banner: i ? a : void 0 -- }, - optimizeDeps: { - exclude: [ - ...u -@@ -139,6 +137,13 @@ const v = (s, l) => d(s) === d(l), e = (s, l) => s ? s === !0 ? !0 : s === l : ! - } - } - }; -+ }, -+ transform(o, r) { -+ if (_ && a && E(r)) -+ return { -+ code: a + o, -+ map: null -+ }; - } - }; - }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ee83a2a9..d4cdc1588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3 +1,368 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + '@pnpm/exe': + specifier: 11.12.0 + version: 11.12.0 + pnpm: + specifier: 11.12.0 + version: 11.12.0 + +packages: + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@pnpm/exe@11.12.0': + resolution: {integrity: sha512-JLuHatntqB+TXtGzIb5BkmzOKzNQcX6CFdZ4Ayy9+hfBCiO/eSEspp9uaawQK4wokYj6O/YNIUOtkDyXdOHzog==} + hasBin: true + + '@pnpm/linux-arm64@11.12.0': + resolution: {integrity: sha512-/Td3e/8VRqPyY2x7Yed3tmYYb3ks8JYk78Opy+3pyA8NNwfJ3xPlZoE2pwQ7DsMiohEWIx44OzOMZccqJbM4wA==} + cpu: [arm64] + os: [linux] + + '@pnpm/linux-x64@11.12.0': + resolution: {integrity: sha512-UJDWZGnfR8qvr0prkD7dp3uZ3obYhDWFl09NykNSZtVQOScW9AlvSlzcjrZdhU2BpqZjD2hZTpUeXmj3JkzXOg==} + cpu: [x64] + os: [linux] + + '@pnpm/linuxstatic-arm64@11.12.0': + resolution: {integrity: sha512-xNWs9L3sAJCR4Ka8vT/pU4cmSz2dI7aFJL8J+vxjseDYG3ad5tLN5xCF0VrKeph4YUQRg/aIb1s9gAi8zsAwgg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/linuxstatic-x64@11.12.0': + resolution: {integrity: sha512-rS+V5q3LJ3fQaaN0ZBNqwj9UokboNED4zLYmP8pEIj3NwpJwmcQzZBsmMWR2zV14NJlw0T+zP8JawpeFsvQnYw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/macos-arm64@11.12.0': + resolution: {integrity: sha512-ws+SLr/qTMWJzWU3c6M6D+s04Mx8ti6km+4/RIAz/cQU3JO1YUPTRNuQ8V0ML81grYHX8JM07oQ9Q2hD7Fo2dQ==} + cpu: [arm64] + os: [darwin] + + '@pnpm/win-arm64@11.12.0': + resolution: {integrity: sha512-Z6eZ3+ou4Dt4FbX0Ht8IbBIwv8S6hhW0143AmftlJ+dLm+06sQ6dQVXsKqBfnUl7XgV3LV2jq22sj6NGEg9vDA==} + cpu: [arm64] + os: [win32] + + '@pnpm/win-x64@11.12.0': + resolution: {integrity: sha512-sg9RhkuSE8OJBq2AHcBrXQY4gBt/6J0DJRKCw8N8fplt6goq69ijsWTxvF5Q1KqdJTgdf9lJBpwINPSAjAK19A==} + cpu: [x64] + os: [win32] + + '@reflink/reflink-darwin-arm64@0.1.19': + resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@reflink/reflink-darwin-x64@0.1.19': + resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@reflink/reflink-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@reflink/reflink-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@reflink/reflink@0.1.19': + resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==} + engines: {node: '>= 10'} + + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + node-gyp@12.4.0: + resolution: {integrity: sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pnpm@11.12.0: + resolution: {integrity: sha512-ggpvvQ2fBMImY4ACrq0eRTQKkTndXcB3wdg+9EqiSByOtmN7TJqmlqPH41uoGOSc8nIT5fK5ETjQm3o+JuiYug==} + engines: {node: '>=22.13'} + hasBin: true + + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + tar@7.5.20: + resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + undici@6.27.0: + resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} + engines: {node: '>=18.17'} + + v8-compile-cache@2.4.0: + resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} + + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + +snapshots: + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@pnpm/exe@11.12.0': + dependencies: + '@reflink/reflink': 0.1.19 + detect-libc: 2.1.2 + optionalDependencies: + '@pnpm/linux-arm64': 11.12.0 + '@pnpm/linux-x64': 11.12.0 + '@pnpm/linuxstatic-arm64': 11.12.0 + '@pnpm/linuxstatic-x64': 11.12.0 + '@pnpm/macos-arm64': 11.12.0 + '@pnpm/win-arm64': 11.12.0 + '@pnpm/win-x64': 11.12.0 + + '@pnpm/linux-arm64@11.12.0': + optional: true + + '@pnpm/linux-x64@11.12.0': + optional: true + + '@pnpm/linuxstatic-arm64@11.12.0': + optional: true + + '@pnpm/linuxstatic-x64@11.12.0': + optional: true + + '@pnpm/macos-arm64@11.12.0': + optional: true + + '@pnpm/win-arm64@11.12.0': + optional: true + + '@pnpm/win-x64@11.12.0': + optional: true + + '@reflink/reflink-darwin-arm64@0.1.19': + optional: true + + '@reflink/reflink-darwin-x64@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-musl@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-musl@0.1.19': + optional: true + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + optional: true + + '@reflink/reflink-win32-x64-msvc@0.1.19': + optional: true + + '@reflink/reflink@0.1.19': + optionalDependencies: + '@reflink/reflink-darwin-arm64': 0.1.19 + '@reflink/reflink-darwin-x64': 0.1.19 + '@reflink/reflink-linux-arm64-gnu': 0.1.19 + '@reflink/reflink-linux-arm64-musl': 0.1.19 + '@reflink/reflink-linux-x64-gnu': 0.1.19 + '@reflink/reflink-linux-x64-musl': 0.1.19 + '@reflink/reflink-win32-arm64-msvc': 0.1.19 + '@reflink/reflink-win32-x64-msvc': 0.1.19 + + abbrev@4.0.0: {} + + chownr@3.0.0: {} + + detect-libc@2.1.2: {} + + env-paths@2.2.1: {} + + exponential-backoff@3.1.3: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + graceful-fs@4.2.11: {} + + isexe@4.0.0: {} + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + node-gyp@12.4.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + graceful-fs: 4.2.11 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.8.5 + tar: 7.5.20 + tinyglobby: 0.2.17 + undici: 6.27.0 + which: 6.0.1 + + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + + picomatch@4.0.5: {} + + pnpm@11.12.0: + dependencies: + '@reflink/reflink': 0.1.19 + node-gyp: 12.4.0 + v8-compile-cache: 2.4.0 + + proc-log@6.1.0: {} + + semver@7.8.5: {} + + tar@7.5.20: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + undici@6.27.0: {} + + v8-compile-cache@2.4.0: {} + + which@6.0.1: + dependencies: + isexe: 4.0.0 + + yallist@5.0.0: {} + +--- lockfileVersion: '9.0' settings: @@ -6,15 +371,18 @@ settings: catalogs: default: + '@ast-grep/cli': + specifier: 0.44.1 + version: 0.44.1 '@biomejs/biome': - specifier: ^2.5.0 - version: 2.5.0 + specifier: ^2.5.3 + version: 2.5.3 '@commitlint/cli': - specifier: ^21.0.2 - version: 21.0.2 + specifier: ^21.2.1 + version: 21.2.1 '@commitlint/config-conventional': - specifier: ^21.0.2 - version: 21.0.2 + specifier: ^21.2.0 + version: 21.2.0 '@cosmjs/amino': specifier: ^0.36.2 version: 0.36.2 @@ -33,27 +401,33 @@ catalogs: '@cosmos-kit/walletconnect': specifier: 2.15.1 version: 2.15.1 + '@effect/atom-react': + specifier: 4.0.0-beta.97 + version: 4.0.0-beta.97 '@effect/openapi-generator': - specifier: 4.0.0-beta.68 - version: 4.0.0-beta.68 + specifier: 4.0.0-beta.97 + version: 4.0.0-beta.97 + '@effect/platform-browser': + specifier: 4.0.0-beta.97 + version: 4.0.0-beta.97 '@effect/platform-node': - specifier: 4.0.0-beta.68 - version: 4.0.0-beta.68 + specifier: 4.0.0-beta.97 + version: 4.0.0-beta.97 '@faker-js/faker': - specifier: ^10.4.0 - version: 10.4.0 + specifier: ^10.5.0 + version: 10.5.0 '@ledgerhq/wallet-api-client': - specifier: ^1.14.5 - version: 1.14.5 + specifier: ^1.15.1 + version: 1.15.1 '@ledgerhq/wallet-api-core': - specifier: ^1.32.1 - version: 1.32.1 + specifier: ^1.34.0 + version: 1.34.0 '@luno-kit/core': specifier: ^0.0.13 version: 0.0.13 '@meshsdk/wallet': - specifier: 1.9.0-beta-40 - version: 1.9.0-beta-40 + specifier: 1.9.1 + version: 1.9.1 '@polkadot/types': specifier: ^16.5.6 version: 16.5.6 @@ -61,17 +435,17 @@ catalogs: specifier: ^14.0.3 version: 14.0.3 '@radix-ui/react-dialog': - specifier: ^1.1.15 - version: 1.1.15 + specifier: ^1.1.19 + version: 1.1.19 '@radix-ui/react-dropdown-menu': - specifier: ^2.1.16 - version: 2.1.16 + specifier: ^2.1.20 + version: 2.1.20 '@radix-ui/react-tooltip': - specifier: ^1.2.8 - version: 1.2.8 + specifier: ^1.2.12 + version: 1.2.12 '@radix-ui/react-visually-hidden': - specifier: ^1.2.4 - version: 1.2.4 + specifier: ^1.2.7 + version: 1.2.7 '@rolldown/plugin-babel': specifier: ^0.2.3 version: 0.2.3 @@ -81,15 +455,18 @@ catalogs: '@safe-global/safe-apps-sdk': specifier: ^9.1.0 version: 9.1.0 + '@solana-mobile/wallet-adapter-mobile': + specifier: ^2.2.3 + version: 2.2.3 '@solana/wallet-adapter-base': specifier: ^0.9.27 version: 0.9.27 - '@solana/wallet-adapter-react': - specifier: ^0.15.39 - version: 0.15.39 '@solana/wallet-adapter-wallets': specifier: ^0.19.38 version: 0.19.38 + '@solana/wallet-standard-wallet-adapter-base': + specifier: ^1.1.4 + version: 1.1.4 '@solana/web3.js': specifier: ^1.98.4 version: 1.98.4 @@ -97,11 +474,11 @@ catalogs: specifier: ^2.2.11 version: 2.2.11 '@tanstack/react-query': - specifier: ^5.100.10 - version: 5.100.10 + specifier: ^5.101.2 + version: 5.101.2 '@tanstack/react-virtual': - specifier: ^3.13.24 - version: 3.13.24 + specifier: ^3.14.5 + version: 3.14.5 '@ton/core': specifier: ^0.63.1 version: 0.63.1 @@ -109,20 +486,20 @@ catalogs: specifier: ^2.4.4 version: 2.4.4 '@tronweb3/tronwallet-abstract-adapter': - specifier: ^1.1.13 - version: 1.1.13 + specifier: ^1.2.0 + version: 1.2.0 '@tronweb3/tronwallet-adapter-bitkeep': - specifier: ^1.1.8 - version: 1.1.8 + specifier: ^1.2.0 + version: 1.2.0 '@tronweb3/tronwallet-adapter-ledger': - specifier: ^1.1.13 - version: 1.1.13 + specifier: ^1.1.14 + version: 1.1.14 '@tronweb3/tronwallet-adapter-tronlink': - specifier: ^1.1.16 - version: 1.1.16 + specifier: ^1.2.0 + version: 1.2.0 '@tronweb3/tronwallet-adapter-walletconnect': - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.0.5 + version: 3.0.5 '@types/lodash.merge': specifier: ^4.6.9 version: 4.6.9 @@ -130,17 +507,20 @@ catalogs: specifier: ^4.5.9 version: 4.5.9 '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.1 + version: 26.1.1 '@types/react': specifier: 19.2.17 version: 19.2.17 '@types/react-dom': specifier: 19.2.3 version: 19.2.3 + '@typescript/native': + specifier: npm:typescript@^7.0.2 + version: 7.0.2 '@vanilla-extract/css': - specifier: ^1.20.1 - version: 1.20.1 + specifier: ^1.21.1 + version: 1.21.1 '@vanilla-extract/dynamic': specifier: ^2.1.5 version: 2.1.5 @@ -148,32 +528,32 @@ catalogs: specifier: ^0.5.7 version: 0.5.7 '@vanilla-extract/sprinkles': - specifier: ^1.6.5 - version: 1.6.5 + specifier: ^1.7.0 + version: 1.7.0 '@vanilla-extract/vite-plugin': - specifier: ^5.2.2 - version: 5.2.2 + specifier: ^5.2.4 + version: 5.2.4 '@vitejs/plugin-react': - specifier: ^6.0.2 - version: 6.0.2 + specifier: ^6.0.3 + version: 6.0.3 '@vitest/browser-playwright': - specifier: ^4.1.9 - version: 4.1.9 - '@xstate/react': - specifier: ^6.1.0 - version: 6.1.0 - '@xstate/store': - specifier: ^3.17.5 - version: 3.17.5 + specifier: ^4.1.10 + version: 4.1.10 + '@wallet-standard/app': + specifier: ^1.1.0 + version: 1.1.0 + '@wallet-standard/base': + specifier: ^1.1.0 + version: 1.1.0 autoprefixer: - specifier: ^10.5.0 - version: 10.5.0 + specifier: ^10.5.2 + version: 10.5.2 babel-plugin-react-compiler: specifier: 1.0.0 version: 1.0.0 bignumber.js: - specifier: ^11.1.1 - version: 11.1.1 + specifier: ^11.1.5 + version: 11.1.5 chain-registry: specifier: 1.69.221 version: 1.69.221 @@ -183,12 +563,9 @@ catalogs: cosmjs-types: specifier: ^0.10.1 version: 0.10.1 - date-fns: - specifier: ^4.1.0 - version: 4.1.0 effect: - specifier: 4.0.0-beta.68 - version: 4.0.0-beta.68 + specifier: 4.0.0-beta.97 + version: 4.0.0-beta.97 eventemitter3: specifier: ^5.0.4 version: 5.0.4 @@ -196,11 +573,17 @@ catalogs: specifier: ^9.1.7 version: 9.1.7 i18next: - specifier: ^26.1.0 - version: 26.1.0 + specifier: ^26.3.6 + version: 26.3.6 i18next-browser-languagedetector: specifier: ^8.2.1 version: 8.2.1 + jsdom: + specifier: ^27.2.0 + version: 27.2.0 + knip: + specifier: ^6.23.0 + version: 6.26.0 lodash.merge: specifier: ^4.6.2 version: 4.6.2 @@ -211,26 +594,23 @@ catalogs: specifier: ^0.0.7 version: 0.0.7 mixpanel-browser: - specifier: ^2.78.0 - version: 2.78.0 + specifier: ^2.81.0 + version: 2.81.0 motion: - specifier: 12.38.0 - version: 12.38.0 + specifier: 12.42.2 + version: 12.42.2 msw: - specifier: ^2.14.6 - version: 2.14.6 + specifier: ^2.15.0 + version: 2.15.0 next: - specifier: ^16.2.9 - version: 16.2.9 + specifier: ^16.2.10 + version: 16.2.10 playwright: - specifier: ^1.61.0 - version: 1.61.0 + specifier: ^1.61.1 + version: 1.61.1 postcss: - specifier: ^8.5.14 - version: 8.5.14 - purify-ts: - specifier: 2.1.4 - version: 2.1.4 + specifier: ^8.5.16 + version: 8.5.16 react: specifier: ^19.2.7 version: 19.2.7 @@ -238,8 +618,8 @@ catalogs: specifier: ^19.2.7 version: 19.2.7 react-i18next: - specifier: ^17.0.7 - version: 17.0.7 + specifier: ^17.0.9 + version: 17.0.9 react-loading-skeleton: specifier: ^3.5.0 version: 3.5.0 @@ -247,17 +627,11 @@ catalogs: specifier: ^7.15.0 version: 7.15.0 recharts: - specifier: ^3.8.1 - version: 3.8.1 - reselect: - specifier: ^5.1.1 - version: 5.1.1 + specifier: ^3.9.2 + version: 3.9.2 rev-dep: - specifier: ^2.15.0 - version: 2.15.0 - rxjs: - specifier: ^7.8.2 - version: 7.8.2 + specifier: ^2.18.0 + version: 2.18.0 serve: specifier: ^14.2.6 version: 14.2.6 @@ -265,72 +639,75 @@ catalogs: specifier: ^7.0.8 version: 7.0.8 tsx: - specifier: ^4.22.4 - version: 4.22.4 + specifier: ^4.23.0 + version: 4.23.0 turbo: - specifier: ^2.9.18 - version: 2.9.18 + specifier: ^2.10.4 + version: 2.10.4 typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: npm:@typescript/typescript6@^6.0.2 + version: 6.0.2 unplugin-macros: specifier: ^0.20.2 version: 0.20.2 viem: - specifier: ^2.48.11 - version: 2.48.11 + specifier: ^2.55.0 + version: 2.55.0 vite: - specifier: ^8.0.16 - version: 8.0.16 + specifier: ^8.1.4 + version: 8.1.4 vite-plugin-node-polyfills: specifier: ^0.28.0 version: 0.28.0 vitest: - specifier: ^4.1.9 - version: 4.1.9 + specifier: ^4.1.10 + version: 4.1.10 vitest-browser-react: specifier: ^2.2.0 version: 2.2.0 wagmi: - specifier: 3.6.14 - version: 3.6.14 - xstate: - specifier: ^5.31.1 - version: 5.31.1 + specifier: 3.7.1 + version: 3.7.1 yaml: specifier: ^2.9.0 version: 2.9.0 +packageExtensionsChecksum: sha256-FZ9pA8g2+w7cg8p8CU69qpPAEhtGVHP/kzcJxOvNvcM= + patchedDependencies: - '@stakekit/rainbowkit@2.2.11': - hash: d028a8df4a214c19e07d959c285fb81eb54f4f1f3cbc199c7331e502cd94fbea - path: patches/@stakekit__rainbowkit@2.2.11.patch - purify-ts: - hash: 5a25e93220157c14f071521854d75c4da38ec0e15fad1989f51e87be19c2fd63 - path: patches/purify-ts.patch + '@stakekit/rainbowkit@2.2.11': d028a8df4a214c19e07d959c285fb81eb54f4f1f3cbc199c7331e502cd94fbea importers: .: devDependencies: + '@ast-grep/cli': + specifier: 'catalog:' + version: 0.44.1 '@biomejs/biome': specifier: 'catalog:' - version: 2.5.0 + version: 2.5.3 '@commitlint/cli': specifier: 'catalog:' - version: 21.0.2(@types/node@25.7.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3) + version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2) '@commitlint/config-conventional': specifier: 'catalog:' - version: 21.0.2 + version: 21.2.0 husky: specifier: 'catalog:' version: 9.1.7 + knip: + specifier: 'catalog:' + version: 6.26.0 rev-dep: specifier: 'catalog:' - version: 2.15.0 + version: 2.18.0 + tsx: + specifier: 'catalog:' + version: 4.23.0 turbo: specifier: 'catalog:' - version: 2.9.18 + version: 2.10.4 packages/examples/with-cdn-script: devDependencies: @@ -345,7 +722,7 @@ importers: version: link:../../widget next: specifier: 'catalog:' - version: 16.2.9(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.85.0) + version: 16.2.10(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.85.0) react: specifier: 'catalog:' version: 19.2.7 @@ -355,16 +732,19 @@ importers: devDependencies: '@types/node': specifier: 'catalog:' - version: 25.7.0 + version: 26.1.1 '@types/react': specifier: 'catalog:' version: 19.2.17 '@types/react-dom': specifier: 'catalog:' version: 19.2.3(@types/react@19.2.17) + '@typescript/native': + specifier: 'catalog:' + version: typescript@7.0.2 typescript: specifier: 'catalog:' - version: 6.0.3 + version: '@typescript/typescript6@6.0.2' packages/examples/with-vite: dependencies: @@ -384,15 +764,15 @@ importers: '@types/react-dom': specifier: 'catalog:' version: 19.2.3(@types/react@19.2.17) - '@vitejs/plugin-react': + '@typescript/native': specifier: 'catalog:' - version: 6.0.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - typescript: + version: typescript@7.0.2 + '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.3 + version: 6.0.3(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) vite: specifier: 'catalog:' - version: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + version: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) packages/examples/with-vite-bundled: dependencies: @@ -400,12 +780,12 @@ importers: specifier: workspace:* version: link:../../widget devDependencies: - typescript: + '@typescript/native': specifier: 'catalog:' - version: 6.0.3 + version: typescript@7.0.2 vite: specifier: 'catalog:' - version: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + version: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) packages/widget: devDependencies: @@ -417,37 +797,43 @@ importers: version: 0.36.2 '@cosmos-kit/core': specifier: 'catalog:' - version: 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + version: 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) '@cosmos-kit/keplr': specifier: 'catalog:' - version: 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@cosmos-kit/leap': specifier: 'catalog:' - version: 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@cosmos-kit/walletconnect': specifier: 'catalog:' - version: 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@effect/atom-react': + specifier: 'catalog:' + version: 4.0.0-beta.97(effect@4.0.0-beta.97)(react@19.2.7)(scheduler@0.27.0) '@effect/openapi-generator': specifier: 'catalog:' - version: 4.0.0-beta.68(@effect/platform-node@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.68) + version: 4.0.0-beta.97(@effect/platform-node@4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.97)(encoding@0.1.13) + '@effect/platform-browser': + specifier: 'catalog:' + version: 4.0.0-beta.97(effect@4.0.0-beta.97) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10) + version: 4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(ioredis@5.10.1)(utf-8-validate@5.0.10) '@faker-js/faker': specifier: 'catalog:' - version: 10.4.0 + version: 10.5.0 '@ledgerhq/wallet-api-client': specifier: 'catalog:' - version: 1.14.5(@ton/crypto@3.3.0)(encoding@0.1.13) + version: 1.15.1(@ton/crypto@3.3.0)(encoding@0.1.13) '@ledgerhq/wallet-api-core': specifier: 'catalog:' - version: 1.32.1(@ton/crypto@3.3.0)(encoding@0.1.13) + version: 1.34.0(@ton/crypto@3.3.0)(encoding@0.1.13) '@luno-kit/core': specifier: 'catalog:' - version: 0.0.13(@dedot/chaintypes@0.123.0(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.0.13(@dedot/chaintypes@0.123.0(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@meshsdk/wallet': specifier: 'catalog:' - version: 1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + version: 1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) '@polkadot/types': specifier: 'catalog:' version: 16.5.6 @@ -456,46 +842,49 @@ importers: version: 14.0.3 '@radix-ui/react-dialog': specifier: 'catalog:' - version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-dropdown-menu': specifier: 'catalog:' - version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 2.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-tooltip': specifier: 'catalog:' - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-visually-hidden': specifier: 'catalog:' - version: 1.2.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@rolldown/plugin-babel': specifier: 'catalog:' - version: 0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) '@safe-global/safe-apps-provider': specifier: 'catalog:' - version: 0.18.6(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 0.18.6(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@safe-global/safe-apps-sdk': specifier: 'catalog:' - version: 9.1.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@solana/wallet-adapter-base': + version: 9.1.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@solana-mobile/wallet-adapter-mobile': specifier: 'catalog:' - version: 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-react': + version: 2.2.3(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + '@solana/wallet-adapter-base': specifier: 'catalog:' - version: 0.15.39(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + version: 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) '@solana/wallet-adapter-wallets': specifier: 'catalog:' - version: 0.19.38(@babel/runtime@7.29.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bs58@5.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(ioredis@5.10.1)(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) + version: 0.19.38(@babel/runtime@7.29.7)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bs58@5.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(ioredis@5.10.1)(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) + '@solana/wallet-standard-wallet-adapter-base': + specifier: 'catalog:' + version: 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0) '@solana/web3.js': specifier: 'catalog:' - version: 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + version: 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) '@stakekit/rainbowkit': specifier: 'catalog:' - version: 2.2.11(patch_hash=d028a8df4a214c19e07d959c285fb81eb54f4f1f3cbc199c7331e502cd94fbea)(@tanstack/react-query@5.100.10(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.6.14) + version: 2.2.11(patch_hash=d028a8df4a214c19e07d959c285fb81eb54f4f1f3cbc199c7331e502cd94fbea)(@tanstack/react-query@5.101.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.7.1) '@tanstack/react-query': specifier: 'catalog:' - version: 5.100.10(react@19.2.7) + version: 5.101.2(react@19.2.7) '@tanstack/react-virtual': specifier: 'catalog:' - version: 3.13.24(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 3.14.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@ton/core': specifier: 'catalog:' version: 0.63.1(@ton/crypto@3.3.0) @@ -504,19 +893,19 @@ importers: version: 2.4.4(encoding@0.1.13) '@tronweb3/tronwallet-abstract-adapter': specifier: 'catalog:' - version: 1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) + version: 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@tronweb3/tronwallet-adapter-bitkeep': specifier: 'catalog:' - version: 1.1.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) + version: 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@tronweb3/tronwallet-adapter-ledger': specifier: 'catalog:' - version: 1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) + version: 1.1.14(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@tronweb3/tronwallet-adapter-tronlink': specifier: 'catalog:' - version: 1.1.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) + version: 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@tronweb3/tronwallet-adapter-walletconnect': specifier: 'catalog:' - version: 3.0.3(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 3.0.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) '@types/lodash.merge': specifier: 'catalog:' version: 4.6.9 @@ -529,42 +918,45 @@ importers: '@types/react-dom': specifier: 'catalog:' version: 19.2.3(@types/react@19.2.17) + '@typescript/native': + specifier: 'catalog:' + version: typescript@7.0.2 '@vanilla-extract/css': specifier: 'catalog:' - version: 1.20.1 + version: 1.21.1 '@vanilla-extract/dynamic': specifier: 'catalog:' version: 2.1.5 '@vanilla-extract/recipes': specifier: 'catalog:' - version: 0.5.7(@vanilla-extract/css@1.20.1) + version: 0.5.7(@vanilla-extract/css@1.21.1) '@vanilla-extract/sprinkles': specifier: 'catalog:' - version: 1.6.5(@vanilla-extract/css@1.20.1) + version: 1.7.0(@vanilla-extract/css@1.21.1) '@vanilla-extract/vite-plugin': specifier: 'catalog:' - version: 5.2.2(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(yaml@2.9.0) + version: 5.2.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 6.0.3(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) '@vitest/browser-playwright': specifier: 'catalog:' - version: 4.1.9(bufferutil@4.0.9)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(playwright@1.61.0)(utf-8-validate@5.0.10)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9) - '@xstate/react': + version: 4.1.10(bufferutil@4.0.9)(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(playwright@1.61.1)(utf-8-validate@5.0.10)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(vitest@4.1.10) + '@wallet-standard/app': specifier: 'catalog:' - version: 6.1.0(@types/react@19.2.17)(react@19.2.7)(xstate@5.31.1) - '@xstate/store': + version: 1.1.0 + '@wallet-standard/base': specifier: 'catalog:' - version: 3.17.5(react@19.2.7) + version: 1.1.0 autoprefixer: specifier: 'catalog:' - version: 10.5.0(postcss@8.5.14) + version: 10.5.2(postcss@8.5.16) babel-plugin-react-compiler: specifier: 'catalog:' version: 1.0.0 bignumber.js: specifier: 'catalog:' - version: 11.1.1 + version: 11.1.5 chain-registry: specifier: 'catalog:' version: 1.69.221 @@ -574,21 +966,21 @@ importers: cosmjs-types: specifier: 'catalog:' version: 0.10.1 - date-fns: - specifier: 'catalog:' - version: 4.1.0 effect: specifier: 'catalog:' - version: 4.0.0-beta.68 + version: 4.0.0-beta.97 eventemitter3: specifier: 'catalog:' version: 5.0.4 i18next: specifier: 'catalog:' - version: 26.1.0(typescript@6.0.3) + version: 26.3.6(typescript@7.0.2) i18next-browser-languagedetector: specifier: 'catalog:' version: 8.2.1 + jsdom: + specifier: 'catalog:' + version: 27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) lodash.merge: specifier: 'catalog:' version: 4.6.2 @@ -597,25 +989,22 @@ importers: version: 4.5.0 mipd: specifier: 'catalog:' - version: 0.0.7(typescript@6.0.3) + version: 0.0.7(typescript@7.0.2) mixpanel-browser: specifier: 'catalog:' - version: 2.78.0 + version: 2.81.0 motion: specifier: 'catalog:' - version: 12.38.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) msw: specifier: 'catalog:' - version: 2.14.6(@types/node@26.0.0)(typescript@6.0.3) + version: 2.15.0(@types/node@26.1.1)(typescript@7.0.2) playwright: specifier: 'catalog:' - version: 1.61.0 + version: 1.61.1 postcss: specifier: 'catalog:' - version: 8.5.14 - purify-ts: - specifier: 'catalog:' - version: 2.1.4(patch_hash=5a25e93220157c14f071521854d75c4da38ec0e15fad1989f51e87be19c2fd63) + version: 8.5.16 react: specifier: 'catalog:' version: 19.2.7 @@ -624,7 +1013,7 @@ importers: version: 19.2.7(react@19.2.7) react-i18next: specifier: 'catalog:' - version: 17.0.7(i18next@26.1.0(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@6.0.3) + version: 17.0.9(i18next@26.3.6(typescript@7.0.2))(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@7.0.2) react-loading-skeleton: specifier: 'catalog:' version: 3.5.0(react@19.2.7) @@ -633,46 +1022,37 @@ importers: version: 7.15.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) recharts: specifier: 'catalog:' - version: 3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1) - reselect: - specifier: 'catalog:' - version: 5.1.1 - rxjs: - specifier: 'catalog:' - version: 7.8.2 + version: 3.9.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1) swagger2openapi: specifier: 'catalog:' version: 7.0.8(encoding@0.1.13) tsx: specifier: 'catalog:' - version: 4.22.4 + version: 4.23.0 typescript: specifier: 'catalog:' - version: 6.0.3 + version: '@typescript/typescript6@6.0.2' unplugin-macros: specifier: 'catalog:' - version: 0.20.2(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(rolldown@1.0.3)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + version: 0.20.2(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(rolldown@1.1.5)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) viem: specifier: 'catalog:' - version: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + version: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) vite: specifier: 'catalog:' - version: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + version: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) vite-plugin-node-polyfills: specifier: 'catalog:' - version: 0.28.0(rollup@4.53.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 0.28.0(rollup@4.53.3)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.9(@types/node@26.0.0)(@vitest/browser-playwright@4.1.9)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(@vitest/browser-playwright@4.1.10)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) vitest-browser-react: specifier: 'catalog:' - version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9) + version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.10) wagmi: specifier: 'catalog:' - version: 3.6.14(33458ed1d2c332a5e309c6e4b0d54aab) - xstate: - specifier: 'catalog:' - version: 5.31.1 + version: 3.7.1(23bf2f6635616981ad1339a134a5f675) yaml: specifier: 'catalog:' version: 2.9.0 @@ -682,8 +1062,8 @@ packages: '@acemir/cssom@0.9.31': resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - '@adobe/css-tools@4.4.4': - resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} '@adraffy/ens-normalize@1.10.1': resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} @@ -700,64 +1080,93 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} + '@ast-grep/cli-darwin-arm64@0.44.1': + resolution: {integrity: sha512-wOzb+IqFy4Pdhvs+PeaHdQJ1VemsI1SgoOGJf+x0J0KBSJ+s/hUg2n1fVJwfsrw/XDIbz9m7PGKTGCLxsgYa1g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@ast-grep/cli-darwin-x64@0.44.1': + resolution: {integrity: sha512-X3OzzsnO9Q3ISQ4qO3jasWk8EZhJA03xfmJHhuS9rGJZZCbNEuqjicrwrDMKK8EgEGJ7MG4oL0BE5qUcn3twLw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@ast-grep/cli-linux-arm64-gnu@0.44.1': + resolution: {integrity: sha512-2F1YBRd9tQnfrGX+4BlkfXWLb2jqNgTmjp0ETLNRLMmSDinR905QsZn2A55qcAZxhQDU982mEzzgT4NduPXIGg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@ast-grep/cli-linux-x64-gnu@0.44.1': + resolution: {integrity: sha512-d5UNoDLT2i6xV4GGqcPnrAR6CB3H078+v3BiqjKxZdZMHFnDe46+i+DxfH1IK6yvJqViqv2F611+wiAAn/s7qw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@ast-grep/cli-win32-arm64-msvc@0.44.1': + resolution: {integrity: sha512-r6K49Z14BLOeJdBSmaoK+TdgtPRqJfJ562F0n0KvRi0qH3o8is8N2dXtEMYKynYm/tdOd65i69Mc1WKSuIeWyA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@ast-grep/cli-win32-ia32-msvc@0.44.1': + resolution: {integrity: sha512-AusXTP5SfUQEL/EkF0fhHoA4H30M7hsVgvsVEZ7PeZrxB69trzUcqKnHC8pBDYTmgIsJsr9dtXjTPDcBauaPiA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@ast-grep/cli-win32-x64-msvc@0.44.1': + resolution: {integrity: sha512-gDMHT17Edmp/tXTDdMSQHd9vJki2Y2WSXq9ycrvjziHwpmnsnc6xlCQ7SEOGA1987vepSpkOkqBCPliP2TVW0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@ast-grep/cli@0.44.1': + resolution: {integrity: sha512-wSjtu0/9xSYzzo/EW31w5pf1qB33tejYKmKXb2nBAlZ5PpYtOqwFmk9H1nywhjzO913beugsKAZYxrwciqW9AQ==} + engines: {node: '>= 12.0.0'} + hasBin: true '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.3': - resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} '@babel/generator@7.29.7': resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} '@babel/helper-globals@7.29.7': resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.29.7': resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -766,10 +1175,6 @@ packages: resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} @@ -778,19 +1183,14 @@ packages: resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.7': resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} @@ -880,8 +1280,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -894,26 +1294,18 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} @@ -922,63 +1314,66 @@ packages: resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} engines: {node: ^22.18.0 || >=24.11.0} + '@base-org/account@2.4.0': + resolution: {integrity: sha512-A4Umpi8B9/pqR78D1Yoze4xHyQaujioVRqqO3d6xuDFw9VRtjg6tK3bPlwE0aW+nVH/ntllCpPa2PbI8Rnjcug==} + '@biglup/is-cid@1.0.3': resolution: {integrity: sha512-R0XPZ/IQhU2TtetSFI9vI+7kJOJYNiCncn5ixEBW+/LNaZCo2HK37Mq3pRNzrM4FryuAkyeqY7Ujmj3I3e3t9g==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} - '@biomejs/biome@2.5.0': - resolution: {integrity: sha512-4kURkd9hAPrdDM3C9n82ycYgx8hvQcW6MjKTEejruj8rK0N8P3OPpdy8BvI8kt3KWY4ycF5XtDOrktetEfhfuw==} + '@biomejs/biome@2.5.3': + resolution: {integrity: sha512-MrJswFdei9EfDwwUy2tQrPDpK0AO+RmMFvBoaaJ6ayBc3sUbHdCE+XG5N8vp+5So41ZupZJQm0roHFFhMGVD7A==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.5.0': - resolution: {integrity: sha512-Mn3Fwi3SA5fgmfCPqmzpWF2DLZnms3BVAhM088nTnGrTZmHS3wwIjcoZPqpXeNgd3DrrLH6xp8vTLIBuJoZiXw==} + '@biomejs/cli-darwin-arm64@2.5.3': + resolution: {integrity: sha512-QhYP9muVQ0nUO5zztFuPbEwi4+94sJWVjaZds9aMi1l/KNZBiUjdiSUrGHsTaMGDXrYl+r4AS2sUKfgH3w+V3g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.5.0': - resolution: {integrity: sha512-rg3VPL5P8mYro6pqlXYXuJWph21slVp3SZtAqWSrkZs40d2gTzYmHF8E/X1iTID25btmNKltNDJ926sqVBp7DQ==} + '@biomejs/cli-darwin-x64@2.5.3': + resolution: {integrity: sha512-NC1Ss13UaW7QZX+y8j44bF7AP0jSJdBl6iRhe0MAkvaSqZy+mWg3GaXsrb+eSoHoGDBtaXWEbMVV0iVN2cZ7cQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.5.0': - resolution: {integrity: sha512-vQdM4oSGaf7ZNeGO9w5+Y8SBtyser9M6znxYbm7Ec8wInxJu1WiKxFYZW5Auj2d80bcVvefuGGRxoFOE0eee8g==} + '@biomejs/cli-linux-arm64-musl@2.5.3': + resolution: {integrity: sha512-fccix0w6xp6csCXgxeC0dU/3ecgRQal0y+cv2SP9ajNlhe7Yrk2Ug7UDe2j9AT9ZDYitkXpvUKgZjjuoYeP4Vg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.5.0': - resolution: {integrity: sha512-tl+LW8fdD96/xdeWtWwc82LIOc5CoY7N2AsogLTp5R4ECErYt+8Jl/N68ezN9vzSiqPTxw6vjcihoLPYKZHrlw==} + '@biomejs/cli-linux-arm64@2.5.3': + resolution: {integrity: sha512-ksx1KWeyYW18ILL04msF/J4ZBtBDN33znYK8Z/aNv/vlBVxL9/g3mGP+omgHJKy4+KWbK87vcmmpmurfNjSgiA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.5.0': - resolution: {integrity: sha512-+9hIcMngJ+yGUahXqZuZ8CoWKJE9SAZsFsM3QDvXpNsLbXZ9lqVzgBhOk/jTSYkOA0GLP9eu3teukqpLUojHMg==} + '@biomejs/cli-linux-x64-musl@2.5.3': + resolution: {integrity: sha512-O/yU9YKRUiHhmcjF2f38PSjseVk3G4VLWYc0G2HWpzdBVREV6G8IGWIVEFf7MFPfWIzNUIvPsEjeAZQIOgnLcQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.5.0': - resolution: {integrity: sha512-zpEGf4RQbFEh8Vt7OmavLyyOzRbtcE9osCqrS1kfvt8jDvxwhKXLSf7n0ebr/ov0RJ9ssP+lhs6C8a9WwFvrQA==} + '@biomejs/cli-linux-x64@2.5.3': + resolution: {integrity: sha512-yMkJtilsgvILDcVkh187aVLTb64xYsrxYajx5kym+r1ULkO5HUOfu9AYKLGQbOVLwJtT2utNw7hhFNg+17mUYA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.5.0': - resolution: {integrity: sha512-jB0wAvTLI4itx5VidqVUejPQFhRUxiZ9l9FvZ26D5fl6t3qme+ZB4PD3bTSeL1vZ8NI2Rx/zj6H9zcESuGHKGw==} + '@biomejs/cli-win32-arm64@2.5.3': + resolution: {integrity: sha512-cX5z+GYwRcqEok0AH3KSfQGgqYd0Nomfp6Fbe1uiTtELE38hdH2k842wQ9wLNaF/JJ7r4rjJQ4VR+ce+fRmQbw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.5.0': - resolution: {integrity: sha512-VT/lF+GId+67j8aDfLkxdxNoVApsPSTbyAtB3jJq0IWTrY77WXfbPfpngxq0bA6JCEv/7k8C9qWjDRKRznDlyw==} + '@biomejs/cli-win32-x64@2.5.3': + resolution: {integrity: sha512-ExSaJWi4/u6+GXCszlSKpWSjKNbDseAYqqkCznsCsZ/4uidZ/BEqsCc5/3ctlq6dfIubdIIRSVLC/PG9xPl70Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -994,8 +1389,8 @@ packages: resolution: {integrity: sha512-e7QVLF+dQMIv9p+p5CWQjMfBmkERYRa2wK2AjyehQZCJnecZ0gvTbRqewdX5VW4mVXf6KUfFyphsxWK46Pg6LA==} engines: {node: '>=14'} - '@cardano-sdk/core@0.43.0': - resolution: {integrity: sha512-hPnZXjObJub0eXV2dDAG2/gEg/vw092RZ1VGMZfBSqavz18Tg/K6jGQ3cOpWZ9d+MqFzZTCB+s5ctdRkYt3idA==} + '@cardano-sdk/core@0.46.12': + resolution: {integrity: sha512-yUA/xBUQMiMqIWiZPvIhM911pL3jNKg4PkZQ8qP9R7yU3NQ5x4RQkZ+zFDlVLxUt+gJiwIW2es0iPd8ObIKCxA==} engines: {node: '>=16.20.2'} peerDependencies: rxjs: ^7.4.0 @@ -1003,26 +1398,12 @@ packages: rxjs: optional: true - '@cardano-sdk/core@0.45.10': - resolution: {integrity: sha512-PU/onQuPgsy0CtFKDlHcozGHMTHrigWztTmKq54tL0TdWRcClXbMh5Q63ALcP388ZouPC1nKomOAooVgyrrEfw==} - engines: {node: '>=16.20.2'} - peerDependencies: - rxjs: ^7.4.0 - peerDependenciesMeta: - rxjs: - optional: true + '@cardano-sdk/core@0.46.15': + resolution: {integrity: sha512-ubfUpDaSSEGxfmdCvV8xN7niXy1cR3HX4dCA/zJH91GpuV5XGGttI5pFbxpqDZubYHU2lw+QQ2kWJJGHQmjbog==} + engines: {node: '>=22'} - '@cardano-sdk/core@0.46.11': - resolution: {integrity: sha512-N/f0Gna41Jsw/KFdulqgpTks4VoeNG1rhTYmGkgtUkMqBTYK+IdaOwMH4QrNxz08VpbOGv76Km3phqGuvTUinQ==} - engines: {node: '>=16.20.2'} - peerDependencies: - rxjs: ^7.4.0 - peerDependenciesMeta: - rxjs: - optional: true - - '@cardano-sdk/crypto@0.1.32': - resolution: {integrity: sha512-RCKFvkzD32QpKQ0jULADVRNmfBNkCwiZl2nlFbkZ3aCrfIex+6/2CizoagJ161fA7lL5/HGuzWfjOg3GX2ax6w==} + '@cardano-sdk/crypto@0.4.5': + resolution: {integrity: sha512-ymliqxdmen5dGVaiMVQ0VnhrwaYUjbPD3sHoMj8NI6MTuxrREp3pLJASREtWhwmv9k+QzDT6CoyuIXnlEQiWZQ==} engines: {node: '>=16.20.2'} peerDependencies: '@dcspark/cardano-multiplatform-lib-asmjs': ^3.1.1 @@ -1036,9 +1417,9 @@ packages: '@dcspark/cardano-multiplatform-lib-nodejs': optional: true - '@cardano-sdk/crypto@0.2.3': - resolution: {integrity: sha512-jTl8rbocV1XO5DBR6+lGY6Owc/bP+wBg5eO3PttTeKhx/J7o99pyuTa5H36a/XTJwqDwKIXV922QxZR+rfjVbA==} - engines: {node: '>=16.20.2'} + '@cardano-sdk/crypto@0.4.7': + resolution: {integrity: sha512-4aCsLOkqaIS/gSGw4SBqNGcCIp6hZ8alQWJx58U/OiOxC43t7i2JpKagdyWfCNekPM14hO7BWER7y31wf4BFxw==} + engines: {node: '>=22'} peerDependencies: '@dcspark/cardano-multiplatform-lib-asmjs': ^3.1.1 '@dcspark/cardano-multiplatform-lib-browser': ^3.1.1 @@ -1051,47 +1432,28 @@ packages: '@dcspark/cardano-multiplatform-lib-nodejs': optional: true - '@cardano-sdk/crypto@0.4.4': - resolution: {integrity: sha512-jvElFox4TPlTZRtjfw0HlkucRD90EeijfhMT0uD0N6ptkn8sRQXUFO+z+1Zcp9v9L2V324N7+2ThpjjBEoUdXQ==} - engines: {node: '>=16.20.2'} - peerDependencies: - '@dcspark/cardano-multiplatform-lib-asmjs': ^3.1.1 - '@dcspark/cardano-multiplatform-lib-browser': ^3.1.1 - '@dcspark/cardano-multiplatform-lib-nodejs': ^3.1.1 - peerDependenciesMeta: - '@dcspark/cardano-multiplatform-lib-asmjs': - optional: true - '@dcspark/cardano-multiplatform-lib-browser': - optional: true - '@dcspark/cardano-multiplatform-lib-nodejs': - optional: true - - '@cardano-sdk/dapp-connector@0.13.25': - resolution: {integrity: sha512-pu+wTbdpfwO6NJ3NhOOyVPS0B08dHqYWlfuqzSpmz6nXHhnLI1w8yi20YZXKRpcajUoAjNdoyNOXJcFTLh6f/A==} - engines: {node: '>=16.20.2'} - - '@cardano-sdk/input-selection@0.13.34': - resolution: {integrity: sha512-/AidYTF9WesLoMc4PHoETxXgrfYEq8GECcikjvLwx1mygmKpok4Lp41Aio7sBasUCLvZ82/yTd3uXIAvec1aCA==} - engines: {node: '>=16.20.2'} + '@cardano-sdk/dapp-connector@0.13.29': + resolution: {integrity: sha512-KiWYAg3Gw7dioaDb4xyFXzOja2Hkni5rhEz4amv4xCpoI/TlYoyzUusjGPA9YZq+xVpbvH8zrpxPLIiw8HflSw==} + engines: {node: '>=22'} - '@cardano-sdk/key-management@0.25.1': - resolution: {integrity: sha512-D99XTIplI2aQnCZtVUKZdmH9wZJQC2WuZL6hTqGZHHFBAeju2zBzGWT21LlcPRlT0/2DP2/OdfIHoHCr2ORp4g==} + '@cardano-sdk/input-selection@0.14.28': + resolution: {integrity: sha512-pbysJUaIbbpesbv/f0XfFPKBb+bLjCmPcMfNJzpePSZBvr8bUcFpnfKtq28KthVdpe2mgL3k9ebTTcBSk7aERw==} engines: {node: '>=16.20.2'} - '@cardano-sdk/util@0.15.7': - resolution: {integrity: sha512-L0f3gXFujRwSSpjzq2W/OwW23fg0gw5S+9R+91He3LgmyfjNygd939eFPCLhwOscsHcJ4AN27UJSYnx3JMKZ0w==} - engines: {node: '>=16.20.2'} - - '@cardano-sdk/util@0.16.0': - resolution: {integrity: sha512-f0tfX8oiauqAFCyyc/o2Ouezyk83QD4zqLl4DUjZNyCtITL8gBHh25Bkw7RUCGEZ+hf6Qms1n0ui0j3wVY7zRg==} - engines: {node: '>=16.20.2'} + '@cardano-sdk/key-management@0.29.17': + resolution: {integrity: sha512-MB57PcG6BKGgeP93oMsHdivaHrCO+DP31Djc7dzM9ysXHbX2OFJBcMFBuM2XdVvrAnY4QxO+16bmvb6+aIK+sw==} + engines: {node: '>=22'} '@cardano-sdk/util@0.17.1': resolution: {integrity: sha512-TCYe+wRguW1WgRlbWqhGPhcSBkzVzdIcCVgDDN7wiQk2dew0EWVqjsKeqDZdfwzy/s2kr/ZOgXIGywBn/Bzu/Q==} engines: {node: '>=16.20.2'} - '@cardanosolutions/json-bigint@1.0.2': - resolution: {integrity: sha512-hRgKDFRR5zW6vv6KoymE1PuhKk8hxA/F5YsH37ghIFIYnWAMbVoQ7xRKAT5AEy9HrqWM6HxNQLIZ3r3omg96/w==} + '@cardano-sdk/util@0.17.2': + resolution: {integrity: sha512-LdY33OQG5H4p0rppHtjnM+akc1poKhz+02T9zsXsbShfTbeU9HXlA0nJ8xj9FNZMs+ozDR0HWrUjDc7fkNZI7w==} + engines: {node: '>=22'} + + '@cardanosolutions/json-bigint@1.1.0': + resolution: {integrity: sha512-Pdgz18cSwLKKgheOqW/dqbzNI+CliNT4AdaKaKY/P++J9qLxIB8MITCurlzbaFWV3W4nmK0CRQwI1yvuArmjFg==} '@chain-registry/client@1.53.355': resolution: {integrity: sha512-x2uBOfBoR2qNbgD0ZkRB1PbLOPL7QwxmNYJyVI1okrtFz1i8p9VR5sg/8DpJIA6p8B3zQlGB7oV5Ga3A09rt3Q==} @@ -1114,90 +1476,101 @@ packages: '@chainsafe/netmask@2.0.0': resolution: {integrity: sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==} + '@coinbase/cdp-sdk@1.52.0': + resolution: {integrity: sha512-3R42LCxGQy/xy7EdeiAvabAESFMRTNNN8Nm1U4AxSPs8fMwazTulLy0NprsW+vuvIMhO2Ih8XInHiXD3DKx6Fw==} + '@coinbase/wallet-sdk@4.3.6': resolution: {integrity: sha512-4q8BNG1ViL4mSAAvPAtpwlOs1gpC+67eQtgIwNvT3xyeyFFd+guwkc8bcX5rTmQhXpqnhzC4f0obACbP9CqMSA==} - '@commitlint/cli@21.0.2': - resolution: {integrity: sha512-YMmfLbqBg+ZRvvmPhc+cilSQFrh/AgzVgCT1U/OifmUZEwPbvCtA8rN//YNaF9d5eoZphxVMGYtmwA2QgQORgg==} + '@commitlint/cli@21.2.1': + resolution: {integrity: sha512-blsZGe29hJ72VGEFVl72IVYX+1vsfINpjA9yWQA6i7OKD/McGEOXg08sKIRKjFk4JvzhV/9n0l3i6NooPLTNfg==} engines: {node: '>=22.12.0'} hasBin: true - '@commitlint/config-conventional@21.0.2': - resolution: {integrity: sha512-P/ZRhryQmkj0Z0dY9FOoRwe3xkwJyyAdtXwt01NT2kuZttcG2CNYp1q5Ci3u+nDT2jcbJRw2kt13Czl1qKNPfg==} + '@commitlint/config-conventional@21.2.0': + resolution: {integrity: sha512-Qf8WRDVcyVd14if6VTWenebxFbKnVnbzPUJjlzjkyJGeHK2xCGd63Dr1XZzj0plXKQb9P0BfOxoc1HVeCo2BWQ==} engines: {node: '>=22.12.0'} - '@commitlint/config-validator@21.0.1': - resolution: {integrity: sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==} + '@commitlint/config-validator@21.2.0': + resolution: {integrity: sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==} engines: {node: '>=22.12.0'} - '@commitlint/ensure@21.0.1': - resolution: {integrity: sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==} + '@commitlint/ensure@21.2.0': + resolution: {integrity: sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==} engines: {node: '>=22.12.0'} '@commitlint/execute-rule@21.0.1': resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==} engines: {node: '>=22.12.0'} - '@commitlint/format@21.0.1': - resolution: {integrity: sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==} + '@commitlint/format@21.2.0': + resolution: {integrity: sha512-c4q64xaav2U83t7k7RyzJerBZurPer7FxUOY0RL5L/6CZijZ7K+s6HIBGIghj0ey1P2+seRX0J9XQYtDued6tg==} engines: {node: '>=22.12.0'} - '@commitlint/is-ignored@21.0.2': - resolution: {integrity: sha512-H5z4t8PC9tUsmZ/o+EptM3Nq8sTFtskAShdcqxCoyzklW5eaVT5xbrDAET2uypzir9Vsj4ZZmBtyKjYe2XqgeQ==} + '@commitlint/is-ignored@21.2.0': + resolution: {integrity: sha512-4/eB0vBN7L88O/oC4ajAEqi7j2ZfNgxl/+11RfAV9YosejZgDXhY2C9VcHnHJhOzPLoSy5P3Mg/46kqeyJfXKw==} engines: {node: '>=22.12.0'} - '@commitlint/lint@21.0.2': - resolution: {integrity: sha512-PnUmLYGeGLfW8oVatR9KpNxSHYAnJOEWlMZzfdeFOUq6WUrFx1fGQaWCWJqMoIll/xPM+GdfJV+tKHZVHhl0Fg==} + '@commitlint/lint@21.2.0': + resolution: {integrity: sha512-ceO5dp9pLjEZ6y6qbq/uXWXDPykqqlTsyzoQ0NzecpisSJhK3kTy9qzQoPeJuWG/IMNdV1lO0RgmzqoAlSi1uw==} engines: {node: '>=22.12.0'} - '@commitlint/load@21.0.2': - resolution: {integrity: sha512-lwUE70hN0/qE/ZRROhbaX65ly/FF12DrqfReLCESo37M0OQCFAf2jRS+2tSCSORq+bm4Kdju7qNDj46uc1QzTA==} + '@commitlint/load@21.2.0': + resolution: {integrity: sha512-RjlzWQqruRwIenJEfZtq7kG97co97nKoHpflE5YnF61tDLXxHPrdWImgzw6VL6MlFyaOcVlk74eBV8ZQmc3oIA==} engines: {node: '>=22.12.0'} - '@commitlint/message@21.0.2': - resolution: {integrity: sha512-5n4aqHGD/FNnom/D5L8i7cYtV+xjuXcBL832C3w9VglEsZzIsoHpJsvxzJ7cgiOsOdc/2jU4t5+7qMHh7GBX3g==} + '@commitlint/message@21.2.0': + resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==} engines: {node: '>=22.12.0'} - '@commitlint/parse@21.0.2': - resolution: {integrity: sha512-QVZJhGHTm+oiuWyEKOCTQ0ZM3mfJ0eGWFeHuj7WzSKEth+UukcCHac9GD8pgdFlg/qGkFWOtyaNd1T8REgagaw==} + '@commitlint/parse@21.2.0': + resolution: {integrity: sha512-QHWxG4d0PLTF634/AdyZ0MQS+CLn5YOuJlCFhMMlSGKFxzYGUetkHBj18xgBD+6fVzUrA2lrCdi/vlS2f/oYXg==} engines: {node: '>=22.12.0'} - '@commitlint/read@21.0.2': - resolution: {integrity: sha512-BtsrnLVycSSKf4Q0gMch4giCj5NNlmcbhc8ra5vONgGtP2IjRDo33bEFtr5Pm+2N+5fXGWb2MksWPrspPfdhdw==} + '@commitlint/read@21.2.1': + resolution: {integrity: sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==} engines: {node: '>=22.12.0'} - '@commitlint/resolve-extends@21.0.1': - resolution: {integrity: sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==} + '@commitlint/resolve-extends@21.2.0': + resolution: {integrity: sha512-4O/1j51+79Wth9s/MGxt/5gs0XYLDgNlYpltQfhAvLE0itusLKs9zruxbiNg1oOkmkb9L9L4USYGjEj7n87NxA==} engines: {node: '>=22.12.0'} - '@commitlint/rules@21.0.2': - resolution: {integrity: sha512-k6tQ69Td7t2qUSIbik8D3TL1q3ZJpkEbV+yLogDzCRAdOxJm4ndhtBNREsLA1/puRfWvzS9eioF2w43WT+hHgQ==} + '@commitlint/rules@21.2.0': + resolution: {integrity: sha512-C2yXMNpiB8ETZKfx5JD8+ExgF8vTU1VQMKPSUUYwqKpw9oJWQBrlXBpdU038mj2WPjof7o9UzFpmTyBeGMZwZg==} engines: {node: '>=22.12.0'} '@commitlint/to-lines@21.0.1': resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==} engines: {node: '>=22.12.0'} - '@commitlint/top-level@21.0.2': - resolution: {integrity: sha512-s9KKM+e+mXgFeIh4n7KmOGAVT3mkJ3Fp1bBYHIK5pjeUwlEMzp/tZfb5u0Poa680AsQTXMEMRxZi1vQ9m2X5ug==} + '@commitlint/top-level@21.2.0': + resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==} engines: {node: '>=22.12.0'} - '@commitlint/types@21.0.1': - resolution: {integrity: sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==} + '@commitlint/types@21.2.0': + resolution: {integrity: sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==} engines: {node: '>=22.12.0'} - '@conventional-changelog/git-client@2.7.0': - resolution: {integrity: sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==} - engines: {node: '>=18'} + '@conventional-changelog/git-client@3.1.0': + resolution: {integrity: sha512-Tqa/gHco2WJWa740NRjOrfKVvzIqxkZpecb8bemaQ8sKM5PXb1UK4uTyTb/1wIqNuOVaDOFxyBdhTIQZn6gdjQ==} + engines: {node: '>=22'} peerDependencies: - conventional-commits-filter: ^5.0.0 - conventional-commits-parser: ^6.4.0 + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.0.1 peerDependenciesMeta: conventional-commits-filter: optional: true conventional-commits-parser: optional: true + '@conventional-changelog/template@1.2.0': + resolution: {integrity: sha512-12qHxvlKjHmP0PQ+17EREgC7lWyLwbph1RKcZQZ7k7ZWGmrxfxC9gadHGfvzr0g0u8BhiBGg3tks93txodlyRQ==} + engines: {node: '>=22'} + + '@conventional-changelog/template@1.2.1': + resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==} + engines: {node: '>=22'} + '@cosmjs/amino@0.32.4': resolution: {integrity: sha512-zKYOt6hPy8obIFtLie/xtygCkH9ZROiQ12UHfKsOkWaZfPQUvVbtgmu6R4Kn1tFLI/SRkw7eqhaogmW/3NYu/Q==} @@ -1297,8 +1670,8 @@ packages: '@cosmjs/proto-signing': '>=0.32.3' '@walletconnect/types': 2.11.0 - '@csstools/color-helpers@6.0.2': - resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} engines: {node: '>=20.19.0'} '@csstools/css-calc@3.2.1': @@ -1308,8 +1681,8 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.1.8': - resolution: {integrity: sha512-3chWb7PRLijpJpPIKkDxdu6IBeO5MrFACND57On0j8OPpc0wZibcGc3xAHrSEbOx/KDRyMHoIxGn0w1PhXMYHw==} + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -1321,8 +1694,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.5': - resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} + '@csstools/css-syntax-patches-for-csstree@1.1.6': + resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -1397,37 +1770,59 @@ packages: resolution: {integrity: sha512-NStEiNulgpo8s1NQvvr/vVUaA31eYSkquOolxritMBiLCdJBM1rgNtOdQ1S2JfrDnn3vrU/UVkCE9ZHIpj5AzQ==} engines: {node: '>=18'} - '@effect/openapi-generator@4.0.0-beta.68': - resolution: {integrity: sha512-+rWHNxb5OGei5A03ThiZKxQt8Q3NCPLIB+Gl5AinKW53gZWJ+eE77m1wrOYOyAQ7Zm8EGkxL1tTYDDdDimeJ6w==} + '@dnsquery/dns-packet@6.1.1': + resolution: {integrity: sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==} + engines: {node: '>=6'} + + '@effect/atom-react@4.0.0-beta.97': + resolution: {integrity: sha512-yKj5KqgDuAayxkVdXzt0kJaqob0QLozqepPVhGj/I0QPIvGSEfX3OaBGt09irGMyqbP4CKMGtiewyHwRwPZSsw==} + peerDependencies: + effect: ^4.0.0-beta.97 + react: ^19.2.4 + scheduler: '*' + + '@effect/openapi-generator@4.0.0-beta.97': + resolution: {integrity: sha512-bT01/6+kMZqs1PQT4XzIwZQDtbpKeo/4JyXVx5Alk/oCaSIOHP1QWgtI472zuk0ZJ9p3VKl2modG3t8WUySvSQ==} hasBin: true peerDependencies: - '@effect/platform-node': ^4.0.0-beta.68 - effect: ^4.0.0-beta.68 + '@effect/platform-node': ^4.0.0-beta.97 + effect: ^4.0.0-beta.97 + + '@effect/platform-browser@4.0.0-beta.97': + resolution: {integrity: sha512-CPF5Wk+OIR4/D6cvI1QEigL/f6cASTv5v8zBn7d2130KB3DFFsSgqplxaZB56vASb4WFBkQ1l8z+dbtosx5RxA==} + peerDependencies: + effect: ^4.0.0-beta.97 - '@effect/platform-node-shared@4.0.0-beta.68': - resolution: {integrity: sha512-a6q/Rid2CQQBSoTIhBnT2oCozeaEvx5DXreSdT4jbvxPSC0j2l0EqcYdHgvEGDqvhocIAezgmSQbcHNcnyDg8w==} + '@effect/platform-node-shared@4.0.0-beta.97': + resolution: {integrity: sha512-sAlygOlDLLERk7fC24f7Aa4G3wkEbGyyMEileHTHU2fThiPYSGMqNRK1LLnNr17m2+JR7BHbycwXkJkM18BAQw==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.68 + effect: ^4.0.0-beta.97 - '@effect/platform-node@4.0.0-beta.68': - resolution: {integrity: sha512-q2WakGBfZ5wIg9WdL4flbIwxC5TNFq1/qroT4HRT9yOl19dLg0khd2LE9ewOybOPrTQHV3PUyV3Sx6Y3eMYTWQ==} + '@effect/platform-node@4.0.0-beta.97': + resolution: {integrity: sha512-Vd40VLh+Y08Bs37KWtbe9AgO2+t3RKZKGX9YC6ZKAcswu4tXR3CwSI7V2MN+GgS+ez/GLmqH1WrzDDlaIAxKPA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.68 + effect: ^4.0.0-beta.97 ioredis: ^5.7.0 - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} @@ -1441,59 +1836,29 @@ packages: '@emurgo/cardano-serialization-lib-nodejs@13.2.0': resolution: {integrity: sha512-Bz1zLGEqBQ0BVkqt1OgMxdBOE3BdUWUd7Ly9Ecr/aUwkA8AV1w1XzBMe4xblmJHnB1XXNlPH4SraXCvO+q0Mig==} - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.1': resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.1': resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.1': resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] + os: [android] '@esbuild/darwin-arm64@0.28.1': resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} @@ -1501,252 +1866,126 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.1': resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.1': resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.1': resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.1': resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.1': resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.1': resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.1': resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.1': resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.1': resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.1': resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.1': resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.1': resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.1': resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.1': resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.1': resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.1': resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.1': resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.1': resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} @@ -1804,27 +2043,27 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} - '@faker-js/faker@10.4.0': - resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} + '@faker-js/faker@10.5.0': + resolution: {integrity: sha512-bsxD8WLS5lIj7aaoCx1YJkktqYj5vlBUE6HWzu2Q51ksrGJ0H737ECCKlFU7Yf8Br45z9t99frBp/J7kzbMPAg==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} '@fivebinaries/coin-selection@3.0.0': resolution: {integrity: sha512-h25Pn1ZA7oqQBQDodGAgIsQt66T2wDge9onBKNqE66WNWL0KJiKJbpij8YOLo5AAlEIg5IS7EB1QjBgDOIg6DQ==} - '@floating-ui/core@1.7.3': - resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - '@floating-ui/dom@1.7.4': - resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - '@floating-ui/react-dom@2.1.6': - resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} '@foxglove/crc@0.0.3': resolution: {integrity: sha512-DjIZsnL3CyP/yQ/vUYA9cjrD0a/8YXejI5ZmsaOiT16cLfZcTwaCxIN01/ys4jsy+dZCQ/9DnWFn7AEFbiMDaA==} @@ -1837,6 +2076,8 @@ packages: '@fractalwagmi/solana-wallet-adapter@0.1.1': resolution: {integrity: sha512-oTZLEuD+zLKXyhZC5tDRMPKPj8iaxKLxXiCjqRfOo4xmSbS2izGRWLJbKMYYsJysn/OI3UJ3P6CWP8WUWi0dZg==} + peerDependencies: + '@solana/web3.js': ^1.98.4 '@harmoniclabs/bigint-utils@1.0.0': resolution: {integrity: sha512-OhZMHcdtH2hHKMlxWFHf71PmKHdoi9ARpjS9mUu0/cd8VWDDjT7VQoQwC5NN/68iyO4O5Dojrvrp9tjG5BDABA==} @@ -1850,9 +2091,8 @@ packages: '@harmoniclabs/bytestring@1.0.0': resolution: {integrity: sha512-d5m10O0okKc6QNX0pSRriFTkk/kNMnMBGbo5X3kEZwKaXTI4tDVoTZBL7bwbYHwOEdSxWJjVtlO9xtB7ZrYZNg==} - '@harmoniclabs/cbor@1.3.0': - resolution: {integrity: sha512-gzRqqcJL8sulc2/6iqRXZdWUCEeK3A+jwJ88sbVNzgk4IeMFQLSFg4Ck8ZBETu/W/q1zdknjNfJYyH1OxVriQA==} - deprecated: update to 1.6.0 + '@harmoniclabs/cbor@1.6.0': + resolution: {integrity: sha512-KI25p8pHI1rmFZC9NYSxATwlCZ+KJdjydpptKebHcw03Iy7M+E8mF+hSnN5dTbS45xw5ZyKUgPLRgLo1sTuIoQ==} '@harmoniclabs/crypto@0.2.5': resolution: {integrity: sha512-t2saWMFWBx8tOHotiYTTfQKhPGpWT4AMLXxq3u0apShVXNV0vgL0gEgSMudBjES/wrKByCqa2xmU70gadz26hA==} @@ -1863,8 +2103,8 @@ packages: '@harmoniclabs/pair@1.0.0': resolution: {integrity: sha512-D9OBowsUsy1LctHxWzd9AngTzoo5x3rBiJ0gu579t41Q23pb+VNx1euEfluUEiaYbgljcl1lb/4D1fFTZd1tRQ==} - '@harmoniclabs/plutus-data@1.2.4': - resolution: {integrity: sha512-cpr6AnJRultH6PJRDriewHEgNLQs2IGLampZrLjmK5shzTsHICD0yD0Zig9eKdcS7dmY6mlzvSpAJWPGeTxbCA==} + '@harmoniclabs/plutus-data@1.2.6': + resolution: {integrity: sha512-rF046GZ07XDpjZBNybALKYSycjxCLzXKbhLylu9pRuZiii5fVXReEfgtLB29TsPBvGY6ZBeiyHgJnLgm+huZBw==} peerDependencies: '@harmoniclabs/bytestring': ^1.0.0 '@harmoniclabs/cbor': ^1.3.0 @@ -1872,12 +2112,12 @@ packages: '@harmoniclabs/uint8array-utils@1.0.4': resolution: {integrity: sha512-Z454prSbX4geXGHyjjcn9vm6u9NsD3VJykv8f8yE1VjIXSPitaLPEnm8u2+B+GMp1chYlLilOq+kW4OvJ6y28A==} - '@harmoniclabs/uplc@1.2.4': - resolution: {integrity: sha512-Px6utj94cO/hQd9NJgVQI8zycsbgh3rAzDeLdZ1m52bo++EuU1GL+arWX3JYso3/3uNrnUFuizjrAIISwQe3Fg==} + '@harmoniclabs/uplc@1.4.1': + resolution: {integrity: sha512-sELKStjxPBPBxBMylU4oBSUe0/8eJe2HqRblNSwrMu8Fso4YpSPDqHZ33iDZ8QAadVUsT5r2EQKX0TLrj7qXvQ==} peerDependencies: '@harmoniclabs/bytestring': ^1.0.0 '@harmoniclabs/cbor': ^1.3.0 - '@harmoniclabs/crypto': ^0.2.4 + '@harmoniclabs/crypto': ^0.3.0-dev0 '@harmoniclabs/pair': ^1.0.0 '@harmoniclabs/plutus-data': ^1.2.4 @@ -2034,35 +2274,35 @@ packages: cpu: [x64] os: [win32] - '@inquirer/ansi@2.0.5': - resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/confirm@6.0.13': - resolution: {integrity: sha512-wkGPC7yJ5WJk1DJ5SX7fzk+gfj4BM8cf5dDDi71B/551xHrdsZVRJOC0WyikXd0pEsb/9cLniuE4atbsMqmFkw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@11.1.10': - resolution: {integrity: sha512-a4Q5BXHQAHa9eO202sTaFCHFYVB3x5fauDuThEAdZ9gfn76pSxiKU7wWcEH0N1O0XmQvNfQNU6QXpiRxmYQx+A==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@2.0.5': - resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/type@4.0.5': - resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2227,8 +2467,8 @@ packages: '@ledgerhq/devices@8.14.2': resolution: {integrity: sha512-T3pnfrsQEC/eJU0XHIqWI6qww+CL1k3NumR2XGWlMz8lVpoZ9HhcuGoCkMevp+8SWmymgyNIIFue3jlNA5KiMw==} - '@ledgerhq/devices@8.15.1': - resolution: {integrity: sha512-o4XEHiwPytnZxYUnOC5JoHX5TPDJpeMXPfXjwhsXVJ9LAbahxQWzC37Iuzk8ZY/oC2yDptzqjs/qNP2y9D9vCA==} + '@ledgerhq/devices@8.16.0': + resolution: {integrity: sha512-brXLPzkvGM3D5YNsWQ25P5G4SmWdSNBed9W8wKoOIRLGdRfvE+bg9mzFty0iZ+aRLBkLoXwX7xKIL9zUi6LBKQ==} '@ledgerhq/devices@8.5.0': resolution: {integrity: sha512-zDB6Pdk6NYYbYis8cWoLLkL9k/IvjhC3hkuuz2lhpJ2AxIs3/PDMUKoyK4DpjPtRyKk1W1lwsnpc6J1i87r4/Q==} @@ -2239,8 +2479,8 @@ packages: '@ledgerhq/errors@6.34.1': resolution: {integrity: sha512-Atkim7yM9bbfWN7OikiE1vbtRLABRz6rDayTHkX0rkPTs6EkMVZSgoNNDGhcYQRlUXrTz9Eko+5ixCLpJQpEWg==} - '@ledgerhq/errors@6.36.0': - resolution: {integrity: sha512-o2Q5hNvf2TzAzlH8ORAozppRbzixRPYDfmSQrP7FOcM997OEH7qDleXgp/uMpvRdxR/t3CJCG+n0i+bU/oYMKA==} + '@ledgerhq/errors@6.37.0': + resolution: {integrity: sha512-T5yiKI5UX7ugeocdTF3TUsCIN2BH41Bio4ZeN410YFjFOf3es08n/5JyMzzKwzRgP0blG3HfBf7s7vJKqCSAeg==} '@ledgerhq/hw-app-trx@6.29.2': resolution: {integrity: sha512-sLb5IiGf2Sqc64iUMcudt+YyJ/J68YhpupxAQDnEsyhDTrxxrn4ihRIxeYTYOSUwsSP/VNZaAuL+zqn3IqsA0w==} @@ -2266,23 +2506,26 @@ packages: '@ledgerhq/hw-transport@6.35.2': resolution: {integrity: sha512-eSXFFqMDAB2Ra/5uqmlru0cUyc2XDQPEKf4ITWHeMms2fHhETw9lcgAEl61vszkiz0RLUVB4VQsLJjj7O8kCvg==} - '@ledgerhq/hw-transport@6.35.4': - resolution: {integrity: sha512-FMVxiniQp0pgSIEBX9CjXYBuIAH9BTm3OcrN+/2LqkB8QBeFZdiwmO4BeN9nc2aWspe9z6kxN3SuUyouedNokA==} + '@ledgerhq/hw-transport@6.35.5': + resolution: {integrity: sha512-P4+wtLewLWgxPtIb90h5kjpzXVlC6f4IBQBmvowVFkInvZt34ffXkX7wa5KfMzu4l3cqCcpNSqtPSCMp+0vuqg==} '@ledgerhq/logs@6.17.0': resolution: {integrity: sha512-yra33g5q/AU7+PwAws+GaVpQGUuxnDREjVBnviJjcaJLVKuLzI4pnj8Bd3nY3fypM5k1yZEYKEXfUuGFUjP2+w==} - '@ledgerhq/wallet-api-client@1.14.5': - resolution: {integrity: sha512-pgRaTdhDUQYnUyA/mAvSW69gYBwHDibF8z/7vqc98VgRlQX8iJwRvHgsvNMGETqqO52lo2ilR0hWhlKaeUXR7g==} + '@ledgerhq/wallet-api-client@1.15.1': + resolution: {integrity: sha512-Lo/5C2g6e+6gT7H0AssMuK+ewvs3u1FpHcY7wZYT0an3gO+muV7BCwhxuVaiDBelR6S9kxFOgWG1D9i/skIFQA==} - '@ledgerhq/wallet-api-core@1.32.1': - resolution: {integrity: sha512-qnJuZoVLPm4U/4VvCtJFy86w3R0TD2fyxVW/TBJkKJtBLi1OgIfwj3yWy9ELbq2uzH7L8juYbO1eATgUB3aZRA==} + '@ledgerhq/wallet-api-core@1.34.0': + resolution: {integrity: sha512-bgIegxFBeH6JidOBP9g0TPs0QZ1wlukXAPLBhFhhKJMrwvtLj4Gp7wcfX9S3bfa88/HM2BS6rcPZmboxycV6TQ==} '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} - '@lit-labs/ssr-dom-shim@1.5.1': - resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==} + '@libp2p/interface@3.2.4': + resolution: {integrity: sha512-o/ZMbsplniVQhz0AW1CinZcfZPEfr7ttu4w7aivrFAs6xDpnJYmN3FTeEgTt93EHs+AEOcIT76V3KMFDZmIEcQ==} + + '@lit-labs/ssr-dom-shim@1.6.0': + resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==} '@lit/react@1.0.8': resolution: {integrity: sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==} @@ -2302,17 +2545,17 @@ packages: '@dedot/chaintypes': ^0.152.0 dedot: ^0.16.0 - '@meshsdk/common@1.9.0-beta-40': - resolution: {integrity: sha512-owMpLDCJAIY5SFcvrh5uIBd7EPKl7vfxLuPFv9ZA/QcbT7YvbzBD/f6H+2ZgCHnNkuHFqoqBPoC+DQEzjeyagQ==} + '@meshsdk/common@1.9.1': + resolution: {integrity: sha512-66l7vcY+h9w71auVaE7RLJYs87DmO+JTEmQ2xknd6F8fAcR73nMxLZunMRbygPoV9gHDBTMYfHoMBZqWt7N6EA==} - '@meshsdk/core-cst@1.9.0-beta-40': - resolution: {integrity: sha512-fP71MGGZ3u99nCYn1+cil7oMhYGTPYN6jbwmuJXTyxrSJe7xA6jBb13WBl4VRm+wII/ME9topxx9BirhQO6bGA==} + '@meshsdk/core-cst@1.9.1': + resolution: {integrity: sha512-1e72mtTehjV/XQWpxtnejhEXyhU1f8CWqoTaQfq2rfBydLJfFs/NDEYfBKBOx697MOWM7VPAHjLvVAENqS57EQ==} - '@meshsdk/transaction@1.9.0-beta-40': - resolution: {integrity: sha512-AP68D1op0MTnuagh7Ti6eqizLuN6m2FVGWuLRdZcdWRwGfWnIxctKENgPkd7QcTgtTfsHrce80GsIw6wy+KHPQ==} + '@meshsdk/transaction@1.9.1': + resolution: {integrity: sha512-FjKSoUi2xFaKJPI1ObQ13UkrCJu+iPW6THP//ZD/f5I1NciaUz3bcoQ7xWcc7El4TCcDunBPp9di6w9JitBR6Q==} - '@meshsdk/wallet@1.9.0-beta-40': - resolution: {integrity: sha512-MEMQEbbC5Ej++VrLt1Gf3nzmtnRieI6NJ/EgoWEmBvMlgj6g5OdDF3DkWJDOSAx38OJCnKnZQ/DN7W3zDmmaGA==} + '@meshsdk/wallet@1.9.1': + resolution: {integrity: sha512-lPovMk86Ex6t1s12CiSJFfxnTM31vXxwB7h0BOnznPgueBts2z0cgvQVtBMlhIKHTmPWW1/gkuEH/5dGMfZ3jA==} '@metamask/object-multiplex@1.3.0': resolution: {integrity: sha512-czcQeVYdSNtabd+NcYQnrM69MciiJyd1qvKH8WM2Id3C0ZiUUX5Xa/MK+/VUk633DBhVOwdNzAKIQ33lGyA+eQ==} @@ -2335,8 +2578,8 @@ packages: '@mimirdev/apps-sdk@3.1.0': resolution: {integrity: sha512-EevWZhaaP9gKcIJeEbokAugx5X0sd8jRae1CX1Uzg5KsVxODjeLLaAE1D90Lj6//JE1NPaEq3QKi9PliSMLVNg==} - '@mixpanel/rrdom@2.0.0-alpha.18.4': - resolution: {integrity: sha512-58sWLQDPRU0VQn8VzB1tx4ujih9X327Vr6xAzbJ4zge9GENm2L696T4TbWvrRGWPW6w99tHIHbv6SvcLhjjmcQ==} + '@mixpanel/rrdom@2.0.0-alpha.18.5': + resolution: {integrity: sha512-RSWgCJmPpAsAs7xwt0+gMeaWytGtsCLEaTTjTCim6CpVUWM+zZSEnGzspz6FDQD69BjIduAah1LVCT/7dI5T+Q==} '@mixpanel/rrweb-plugin-console-record@2.0.0-alpha.18.4': resolution: {integrity: sha512-gGxEnNpfWurQht+fpSJbwPV60ug0LAENlk4Ux3XRmYooNJGPBO1TiQHcM7g0yhrKf4tfbTiKKZ4EXzFlOZs/pw==} @@ -2344,15 +2587,18 @@ packages: '@mixpanel/rrweb': ^2.0.0-alpha.18 '@mixpanel/rrweb-utils': ^2.0.0-alpha.18 - '@mixpanel/rrweb-snapshot@2.0.0-alpha.18.4': - resolution: {integrity: sha512-ubLGwgPiMMi0rl7zJRh1uMScQ480lT95tkHkIAHkiZOemMY4JSkYZh2v9fpMNwJhaGwB5n/76KI7Cwy8F5qixQ==} + '@mixpanel/rrweb-snapshot@2.0.0-alpha.18.5': + resolution: {integrity: sha512-Ys5VRrHAG4cWMc5QSZRcmhtX5ySPgid3dofVVL1L6h0tL74vcOXO4y+HebHWV2p/iEQg80fmWKaB6HZ5p7+kbA==} - '@mixpanel/rrweb-types@2.0.0-alpha.18.4': - resolution: {integrity: sha512-7kRuk7pK6Firrb26Mm235Po7s/z+9k0gUKZU/DZkzg5U1yQRcsu4byIrnWTiTQr+LFLUrIiyRKnpGK/QneAhTw==} + '@mixpanel/rrweb-types@2.0.0-alpha.18.5': + resolution: {integrity: sha512-upLJEkRB40FGAVJpnfepQXMj/RuBjuC4UGGEKAiZSi17FG4Aqt0F+vXuB+9WicizhQf3vUW89K26Ay0VtbCSiQ==} '@mixpanel/rrweb-utils@2.0.0-alpha.18.4': resolution: {integrity: sha512-c3nUbQl19kxHjf8nowFMeXlJw0ZqLesIVBb9t4g1nC4WtaNEPkFotWRdGt5V2cJNQ+aY38/v2uYb8Ren4IcdSQ==} + '@mixpanel/rrweb-utils@2.0.0-alpha.18.5': + resolution: {integrity: sha512-Gi35ktM6991Zso0ABYf+9kkcldpPzialQtK5tTIkCSEFbtrOD1S7OLAM9dMqVeC2N9fUpw3mFGvuAiXgxsDpiw==} + '@mixpanel/rrweb@2.0.0-alpha.18.4': resolution: {integrity: sha512-ICpEYDFEEiCoUuQg+de3VvQCsolF4lNHfEM9DBp5Pwuc7EgXqwWV4wUpiRF/NbhoKk+82X1Qe+oqqlKJb/CGFw==} @@ -2393,33 +2639,33 @@ packages: resolution: {integrity: sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==} engines: {node: '>= 18'} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} cpu: [arm64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==} cpu: [x64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==} cpu: [arm64] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==} cpu: [arm] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==} cpu: [x64] os: [linux] - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==} cpu: [x64] os: [win32] @@ -2427,8 +2673,8 @@ packages: resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} - '@multiformats/dns@1.0.10': - resolution: {integrity: sha512-6X200ceQLns0b/CU0S/So16tGjB5eIXHJ1xvJMPoWaKFHWSgfpW2EhkWJrqap4U3+c37zcowVR0ToPXeYEL7Vw==} + '@multiformats/dns@1.0.15': + resolution: {integrity: sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==} '@multiformats/mafmt@12.1.6': resolution: {integrity: sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==} @@ -2436,160 +2682,396 @@ packages: '@multiformats/multiaddr@12.5.1': resolution: {integrity: sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==} - '@napi-rs/wasm-runtime@1.1.5': - resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + '@multiformats/multiaddr@13.0.3': + resolution: {integrity: sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==} + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.2.9': - resolution: {integrity: sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==} + '@next/env@16.2.10': + resolution: {integrity: sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==} - '@next/swc-darwin-arm64@16.2.9': - resolution: {integrity: sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==} + '@next/swc-darwin-arm64@16.2.10': + resolution: {integrity: sha512-v9IdJCa0H0mbo+8z5zwUpOk1Vj7RjkcI5uNYf5Ws1y6szf/p3Mzl9hLaST8SCt6L9h8NGnruZcd2+o0NTNwDhA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.9': - resolution: {integrity: sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==} + '@next/swc-darwin-x64@16.2.10': + resolution: {integrity: sha512-17IS0jJRViROGmA9uGdNR8VPJpfbnaVG7E9qhso5jDLkmyd0lSDORWxbcKINzcFqzZqGwGtMSnrFRxBpuUYjLQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.9': - resolution: {integrity: sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==} + '@next/swc-linux-arm64-gnu@16.2.10': + resolution: {integrity: sha512-GRQRsRtuciNJvB54AvvuQTiq0oZtFwa1owQqtZD8wwnGpM2L39MV22kpI72YSXLKIyY40LC66EiLFv4PiicXxg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@16.2.10': + resolution: {integrity: sha512-zkN9MQYS7UQBro+FnISUq1itaQjXI9xqISzuQ+2bc921NcJ1x4yPCqrn77tVN6/dOOXaaWVX3k6/bR07pPwK+A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@16.2.10': + resolution: {integrity: sha512-iCVJnwvrPYECvA6WM/7+oo+OiTvedIKLxtCLAZP4xZR3nXa1zmzZyLPbYCmWvpd4CvMYF1EMTafd0ii3DygLvA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@16.2.10': + resolution: {integrity: sha512-ov2g4H0dHY9bPoOU83m91hWT7Iq5qy13bUnyyshLU3HGR1Ownn0X9QpmDPc5iIUaahTp7f7LeGAhV4DSFtackw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@16.2.10': + resolution: {integrity: sha512-DwAnhLX76HQiFFQNgWlcK+JzlnD1rZ+UK/WY0ZMI/deXpvgnesjNYrqcfo1JzBuz4Kf7o3brIBL0glI1junatA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.2.10': + resolution: {integrity: sha512-0JXq3b85Jk9Jg4ntLUbXSPvoDw3gpZou7twuKdoFG2jOw635v7+IiXfTaa0TxVMyx78pUjnrVYwLgjKfX4e6/A==} engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@ngraveio/bc-ur@1.1.13': + resolution: {integrity: sha512-j73akJMV4+vLR2yQ4AphPIT5HZmxVjn/LxpL7YHoINnXoH6ccc90Zzck6/n6a3bCXOVZwBxq+YHwbAKRV+P8Zg==} + + '@noble/ciphers@1.2.1': + resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.2.0': + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.8.0': + resolution: {integrity: sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.8.1': + resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.2': + resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@2.2.0': + resolution: {integrity: sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@1.1.5': + resolution: {integrity: sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==} + + '@noble/hashes@1.3.2': + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + + '@noble/hashes@1.6.0': + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.7.0': + resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.7.1': + resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + + '@noble/secp256k1@1.7.1': + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@oxc-parser/binding-android-arm-eabi@0.137.0': + resolution: {integrity: sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.137.0': + resolution: {integrity: sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.137.0': + resolution: {integrity: sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.137.0': + resolution: {integrity: sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.137.0': + resolution: {integrity: sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + resolution: {integrity: sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + resolution: {integrity: sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + resolution: {integrity: sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + resolution: {integrity: sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + resolution: {integrity: sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + resolution: {integrity: sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + resolution: {integrity: sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] - '@next/swc-linux-arm64-musl@16.2.9': - resolution: {integrity: sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==} - engines: {node: '>= 10'} - cpu: [arm64] + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + resolution: {integrity: sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] os: [linux] - libc: [musl] + libc: [glibc] - '@next/swc-linux-x64-gnu@16.2.9': - resolution: {integrity: sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==} - engines: {node: '>= 10'} + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + resolution: {integrity: sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.9': - resolution: {integrity: sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==} - engines: {node: '>= 10'} + '@oxc-parser/binding-linux-x64-musl@0.137.0': + resolution: {integrity: sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.9': - resolution: {integrity: sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==} - engines: {node: '>= 10'} + '@oxc-parser/binding-openharmony-arm64@0.137.0': + resolution: {integrity: sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.2.9': - resolution: {integrity: sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@ngraveio/bc-ur@1.1.13': - resolution: {integrity: sha512-j73akJMV4+vLR2yQ4AphPIT5HZmxVjn/LxpL7YHoINnXoH6ccc90Zzck6/n6a3bCXOVZwBxq+YHwbAKRV+P8Zg==} + os: [openharmony] - '@noble/ciphers@1.2.1': - resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} - engines: {node: ^14.21.3 || >=16} + '@oxc-parser/binding-wasm32-wasi@0.137.0': + resolution: {integrity: sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + resolution: {integrity: sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] - '@noble/curves@1.2.0': - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + resolution: {integrity: sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] - '@noble/curves@1.4.2': - resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + resolution: {integrity: sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] - '@noble/curves@1.7.0': - resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} - engines: {node: ^14.21.3 || >=16} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} - '@noble/curves@1.8.0': - resolution: {integrity: sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==} - engines: {node: ^14.21.3 || >=16} + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} - '@noble/curves@1.8.1': - resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + resolution: {integrity: sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==} + cpu: [arm] + os: [android] - '@noble/curves@1.9.1': - resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-android-arm64@11.21.3': + resolution: {integrity: sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==} + cpu: [arm64] + os: [android] - '@noble/curves@1.9.2': - resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-darwin-arm64@11.21.3': + resolution: {integrity: sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==} + cpu: [arm64] + os: [darwin] - '@noble/curves@1.9.7': - resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-darwin-x64@11.21.3': + resolution: {integrity: sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==} + cpu: [x64] + os: [darwin] - '@noble/curves@2.2.0': - resolution: {integrity: sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==} - engines: {node: '>= 20.19.0'} + '@oxc-resolver/binding-freebsd-x64@11.21.3': + resolution: {integrity: sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==} + cpu: [x64] + os: [freebsd] - '@noble/hashes@1.1.5': - resolution: {integrity: sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + resolution: {integrity: sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==} + cpu: [arm] + os: [linux] - '@noble/hashes@1.3.2': - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + resolution: {integrity: sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==} + cpu: [arm] + os: [linux] - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + resolution: {integrity: sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@noble/hashes@1.6.0': - resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + resolution: {integrity: sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==} + cpu: [arm64] + os: [linux] + libc: [musl] - '@noble/hashes@1.7.0': - resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + resolution: {integrity: sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] - '@noble/hashes@1.7.1': - resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + resolution: {integrity: sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==} + cpu: [riscv64] + os: [linux] + libc: [glibc] - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + resolution: {integrity: sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==} + cpu: [riscv64] + os: [linux] + libc: [musl] - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} - engines: {node: '>= 20.19.0'} + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + resolution: {integrity: sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] - '@noble/secp256k1@1.7.1': - resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + resolution: {integrity: sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==} + cpu: [x64] + os: [linux] + libc: [glibc] - '@open-draft/deferred-promise@2.2.0': - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + resolution: {integrity: sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==} + cpu: [x64] + os: [linux] + libc: [musl] - '@open-draft/deferred-promise@3.0.0': - resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + resolution: {integrity: sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==} + cpu: [arm64] + os: [openharmony] - '@open-draft/logger@0.3.0': - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + '@oxc-resolver/binding-wasm32-wasi@11.21.3': + resolution: {integrity: sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] - '@open-draft/until@2.1.0': - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + resolution: {integrity: sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==} + cpu: [arm64] + os: [win32] - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + resolution: {integrity: sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==} + cpu: [x64] + os: [win32] '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} @@ -2697,6 +3179,9 @@ packages: '@solana/web3.js': ^1.50.1 bs58: ^4.0.1 + '@phosphor-icons/webcomponents@2.1.5': + resolution: {integrity: sha512-JcvQkZxvcX2jK+QCclm8+e8HXqtdFW9xV4/kk2aL9Y3dJA2oQVt+pzbv1orkumz3rfx4K9mn9fDoMr1He1yr7Q==} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -2868,11 +3353,11 @@ packages: '@protobufjs/utf8@1.1.1': resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/primitive@1.1.5': + resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==} - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + '@radix-ui/react-arrow@1.1.11': + resolution: {integrity: sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2884,8 +3369,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.7': - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + '@radix-ui/react-collection@1.1.12': + resolution: {integrity: sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2897,8 +3382,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2906,8 +3391,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-context@1.1.2': - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + '@radix-ui/react-context@1.2.0': + resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2915,8 +3400,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.15': - resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + '@radix-ui/react-dialog@1.1.19': + resolution: {integrity: sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2928,8 +3413,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-direction@1.1.1': - resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + '@radix-ui/react-direction@1.1.2': + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2937,8 +3422,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + '@radix-ui/react-dismissable-layer@1.1.15': + resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2950,8 +3435,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.16': - resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + '@radix-ui/react-dropdown-menu@2.1.20': + resolution: {integrity: sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2963,8 +3448,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.3': - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + '@radix-ui/react-focus-guards@1.1.4': + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2972,8 +3457,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.7': - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + '@radix-ui/react-focus-scope@1.1.12': + resolution: {integrity: sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2985,30 +3470,17 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-menu@2.1.16': - resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + '@radix-ui/react-menu@2.1.20': + resolution: {integrity: sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3020,8 +3492,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + '@radix-ui/react-popper@1.3.3': + resolution: {integrity: sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3033,8 +3505,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.5': - resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + '@radix-ui/react-portal@1.1.13': + resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3046,8 +3518,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + '@radix-ui/react-presence@1.1.7': + resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3059,8 +3531,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.4': - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3072,8 +3544,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.11': - resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + '@radix-ui/react-roving-focus@1.1.15': + resolution: {integrity: sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3085,17 +3557,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.2.4': - resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3103,8 +3566,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-tooltip@1.2.8': - resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + '@radix-ui/react-tooltip@1.2.12': + resolution: {integrity: sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3116,17 +3579,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + '@radix-ui/react-use-callback-ref@1.1.2': + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3134,8 +3588,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3143,8 +3597,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3152,8 +3606,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + '@radix-ui/react-use-is-hydrated@0.1.1': + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3161,8 +3615,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3170,8 +3624,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + '@radix-ui/react-use-rect@1.1.2': + resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3179,21 +3633,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + '@radix-ui/react-use-size@1.1.2': + resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-visually-hidden@1.2.4': - resolution: {integrity: sha512-kaeiyGCe844dkb9AVF+rb4yTyb1LiLN/e3es3nLiRyN4dC8AduBYPMnnNlDjX2VDOcvDEiPnRNMJeWCfsX0txg==} + '@radix-ui/react-visually-hidden@1.2.7': + resolution: {integrity: sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3205,8 +3655,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@radix-ui/rect@1.1.2': + resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} '@react-native-async-storage/async-storage@1.24.0': resolution: {integrity: sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==} @@ -3279,196 +3729,196 @@ packages: '@reown/appkit-common@1.7.2': resolution: {integrity: sha512-DZkl3P5+Iw3TmsitWmWxYbuSCox8iuzngNp/XhbNDJd7t4Cj4akaIUxSEeCajNDiGHlu4HZnfyM1swWsOJ0cOw==} - '@reown/appkit-common@1.7.20': - resolution: {integrity: sha512-p1XtJvY2Mf4oEuPD1O8UGpVFJpC094sfoIVQCPKguqABEckTymIap0AOqSokRI1LXAx1EhNJogGNRfekMllJ/Q==} - '@reown/appkit-common@1.7.8': resolution: {integrity: sha512-ridIhc/x6JOp7KbDdwGKY4zwf8/iK8EYBl+HtWrruutSLwZyVi5P8WaZa+8iajL6LcDcDF7LoyLwMTym7SRuwQ==} + '@reown/appkit-common@1.8.20': + resolution: {integrity: sha512-x6Yc8FaZZaEnsRCu6crYD/s1MA+Ffk35rJNZpug8I26iAJgi+o3rar/0lwZ70dl/QA9ytnwFUFWzhXqkT3O7Og==} + '@reown/appkit-controllers@1.7.2': resolution: {integrity: sha512-KCN/VOg+bgwaX5kcxcdN8Xq8YXnchMeZOvmbCltPEFDzaLRUWmqk9tNu1OVml0434iGMNo6hcVimIiwz6oaL3Q==} - '@reown/appkit-controllers@1.7.20': - resolution: {integrity: sha512-/bKmExgsiMRgbV1/L6Xxexw7pC77lf83fFXLy8TzzofDVDmwt5l5Ixh8vfcd26VM/ZkTd0aH5L11Rw5DLixOPw==} - '@reown/appkit-controllers@1.7.8': resolution: {integrity: sha512-IdXlJlivrlj6m63VsGLsjtPHHsTWvKGVzWIP1fXZHVqmK+rZCBDjCi9j267Rb9/nYRGHWBtlFQhO8dK35WfeDA==} - '@reown/appkit-pay@1.7.20': - resolution: {integrity: sha512-AkP16oRjShvSxRygqGgrnKmO8r5Eqw1uHBrL4h6h8ViK/RiW/U1DlMJTENFvMYLpUsu4K4DjC+SXvxKnL2o+hg==} + '@reown/appkit-controllers@1.8.20': + resolution: {integrity: sha512-2Gdi/ScftfBK3MwfAJcf/bGRByHgHzaAK8rCzKrYYUqLjbRFR9I/uq7gd6D+w4Xm+NRCpdiKFJY/0zPSfq+gpw==} '@reown/appkit-pay@1.7.8': resolution: {integrity: sha512-OSGQ+QJkXx0FEEjlpQqIhT8zGJKOoHzVnyy/0QFrl3WrQTjCzg0L6+i91Ad5Iy1zb6V5JjqtfIFpRVRWN4M3pw==} + '@reown/appkit-pay@1.8.20': + resolution: {integrity: sha512-JEoTVetCWNFJnbkYGtwvdWa7ZW7xF0uOH+jmJvnAbYSXTR+Bd8HdQOMn/AoWulSu6qmjKjNpZQmNjkIUf5yiUw==} + '@reown/appkit-polyfills@1.7.2': resolution: {integrity: sha512-TxCVSh9dV2tf1u+OzjzLjAwj7WHhBFufHlJ36tDp5vjXeUUne8KvYUS85Zsyg4Y9Yeh+hdSIOdL2oDCqlRxCmw==} - '@reown/appkit-polyfills@1.7.20': - resolution: {integrity: sha512-WLd6T7kRpa8wlSt0Wk9HND+JYEdAgFbAY2N8NYoHfb9m1ifXLua1ZzoUJggJkF3FxxdqC1fE7bKWzEa7OYoHDg==} - '@reown/appkit-polyfills@1.7.8': resolution: {integrity: sha512-W/kq786dcHHAuJ3IV2prRLEgD/2iOey4ueMHf1sIFjhhCGMynMkhsOhQMUH0tzodPqUgAC494z4bpIDYjwWXaA==} + '@reown/appkit-polyfills@1.8.20': + resolution: {integrity: sha512-88qJedSBYkhCZxeyODcQhqKt45OkS28VbZGNsay6t7FexvPcoibYMtkHaP79ojE8OuhV7k3FlrssrjPES3OFCw==} + '@reown/appkit-scaffold-ui@1.7.2': resolution: {integrity: sha512-2Aifk5d23e40ijUipsN3qAMIB1Aphm2ZgsRQ+UvKRb838xR1oRs+MOsfDWgXhnccXWKbjPqyapZ25eDFyPYPNw==} - '@reown/appkit-scaffold-ui@1.7.20': - resolution: {integrity: sha512-fHNT5ZXzdkh4DnEjwpydPEnt2hcocASobsdH5CrzU0F4G+AWEWdVE8ILzHMQMQ17QwjYEj/i+/YQtqOKUqeISA==} - '@reown/appkit-scaffold-ui@1.7.8': resolution: {integrity: sha512-RCeHhAwOrIgcvHwYlNWMcIDibdI91waaoEYBGw71inE0kDB8uZbE7tE6DAXJmDkvl0qPh+DqlC4QbJLF1FVYdQ==} + '@reown/appkit-scaffold-ui@1.8.20': + resolution: {integrity: sha512-Vx0qib30UUbcKT8VeNcCcZkkhtaOT28TtClQ8nKC0EdOkLA08Lxt0j32XpEat2c4Fl6xzAIIOuZO7HRBTxMfzA==} + '@reown/appkit-ui@1.7.2': resolution: {integrity: sha512-fZv8K7Df6A/TlTIWD/9ike1HwK56WfzYpHN1/yqnR/BnyOb3CKroNQxmRTmjeLlnwKWkltlOf3yx+Y6ucKMk6Q==} - '@reown/appkit-ui@1.7.20': - resolution: {integrity: sha512-XzgBi8TRo9y03Gb+NOho8L5vAQ1w1t5oGc1KWDILYFN2dFtUxpPAfitXE86FrMivoAFd4MM4LboYT/yAC6srMQ==} - '@reown/appkit-ui@1.7.8': resolution: {integrity: sha512-1hjCKjf6FLMFzrulhl0Y9Vb9Fu4royE+SXCPSWh4VhZhWqlzUFc7kutnZKx8XZFVQH4pbBvY62SpRC93gqoHow==} + '@reown/appkit-ui@1.8.20': + resolution: {integrity: sha512-ObWhgu+4capnVhxtEG0VMRtGHwr6bso9VUcxDIfUDpjNAMi1HQBvXiBJ/R098Gvjmjz0QLwNe8YNVe50KaCTVw==} + '@reown/appkit-utils@1.7.2': resolution: {integrity: sha512-Z3gQnMPQopBdf1XEuptbf+/xVl9Hy0+yoK3K9pBb2hDdYNqJgJ4dXComhlRT8LjXFCQe1ZW0pVZTXmGQvOZ/OQ==} peerDependencies: valtio: 1.13.2 - '@reown/appkit-utils@1.7.20': - resolution: {integrity: sha512-tfVDwe0Rs3xrBDPyy2IYgU8iBpB5xtKevx4MHo2RPIWoY393piVTjmL47iBD8AH9ANLxqXyFEIjxGyuIlqdYAg==} - peerDependencies: - valtio: 2.1.5 - '@reown/appkit-utils@1.7.8': resolution: {integrity: sha512-8X7UvmE8GiaoitCwNoB86pttHgQtzy4ryHZM9kQpvjQ0ULpiER44t1qpVLXNM4X35O0v18W0Dk60DnYRMH2WRw==} peerDependencies: valtio: 1.13.2 + '@reown/appkit-utils@1.8.20': + resolution: {integrity: sha512-ZVPUhZm/8TbR5MI1GzhazpUShrjVhZCC8Fyc7m+VXSPVZkPiW7K2AtTDAqhfYeStvBSJIvC6jbKwhlaWGR1TDQ==} + peerDependencies: + valtio: 2.1.7 + '@reown/appkit-wallet@1.7.2': resolution: {integrity: sha512-WQ0ykk5TwsjOcUL62ajT1bhZYdFZl0HjwwAH9LYvtKYdyZcF0Ps4+y2H4HHYOc03Q+LKOHEfrFztMBLXPTxwZA==} - '@reown/appkit-wallet@1.7.20': - resolution: {integrity: sha512-Wx2M6cDg07coWuR3zgvWCZt4e9GPmXtwKLQvDPr39gkXcAvMCSI+LIqA0W8jwLmNFvqs29qHmWqQM0Pzar34Qg==} - '@reown/appkit-wallet@1.7.8': resolution: {integrity: sha512-kspz32EwHIOT/eg/ZQbFPxgXq0B/olDOj3YMu7gvLEFz4xyOFd/wgzxxAXkp5LbG4Cp++s/elh79rVNmVFdB9A==} + '@reown/appkit-wallet@1.8.20': + resolution: {integrity: sha512-fEYnCQTQFSi8qtGfdQp7BHAVD8yiijPgjXhel0jFLKTX5leufGKjrERtnn2vABoUHQucwQzI+Q958Bnx/lHE2w==} + '@reown/appkit@1.7.2': resolution: {integrity: sha512-oo/evAyVxwc33i8ZNQ0+A/VE6vyTyzL3NBJmAe3I4vobgQeiobxMM0boKyLRMMbJggPn8DtoAAyG4GfpKaUPzQ==} - '@reown/appkit@1.7.20': - resolution: {integrity: sha512-VW/2sFGHiboDwpSMPnKZ9mABL+rPHPFagwfYqt3EO1JPHPRePmYKXianM8Owc4kgv9dFVMvUif9gHW/nX53+VA==} - '@reown/appkit@1.7.8': resolution: {integrity: sha512-51kTleozhA618T1UvMghkhKfaPcc9JlKwLJ5uV+riHyvSoWPKPRIa5A6M1Wano5puNyW0s3fwywhyqTHSilkaA==} - '@rev-dep/darwin-arm64@2.15.0': - resolution: {integrity: sha512-EH9bT3TQuW9KXBgSLsfTBcK36TqPgcDKQmHl7gZxsN1IMCOCU/WMHX9tw69cIgEeb+VokRQuRYn5JFYQtS6csg==} + '@reown/appkit@1.8.20': + resolution: {integrity: sha512-+OI1TWg3XcMRhZ5RGVF0+AjkUvTPpODP6HFIgmxsV8HCf6bfE+jaYYE+JJcaz05TYJJ2TCXSYDyRfRvjSUzMzg==} + + '@rev-dep/darwin-arm64@2.18.0': + resolution: {integrity: sha512-D/5XwCFDIEDKU838Lewy5E8qAbJLeCcZuz3AJHp8nRy4GavGdt/oir7Ll2C1EOlk+8P+r50SrnFJ8LcW7OhclQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@rev-dep/linux-x64@2.15.0': - resolution: {integrity: sha512-uwjL+vPM9mff102Xo06F7xiCcnDPaa1R8vPZtARo8bXe7pAo+WfAd6qdkSRE4lbir3hl1Y/XOlvezoiEyrzQ3A==} + '@rev-dep/linux-x64@2.18.0': + resolution: {integrity: sha512-n3GJFA2FA6dXnfEZHjQ2kgXbP2g0tPBO84CzLDvOIY+zlAHyA/UlPaObennR0B/2Qs3VlP99Ch9ulr94SFhfgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@rev-dep/win32-x64@2.15.0': - resolution: {integrity: sha512-6pRsB2f3tebulqx2sBfo2UlX1XPuKjuVnXmiTYw2C4ArCuBdEYqtBLHNXmIMQDDhNN422HOrSM7yZE6TZbyKLQ==} + '@rev-dep/win32-x64@2.18.0': + resolution: {integrity: sha512-I8L9TEmvP2bgID0R5HeMpjjx2pxrmEihuE+bACvRyz0gqrZreDbKp4Vz6F7GY7F7Lbaz2utdjOJS+noRgAmeVQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@rolldown/binding-android-arm64@1.0.3': - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.3': - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.3': - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.3': - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.3': - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.3': - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.3': - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.3': - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.3': - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.3': - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.3': - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.3': - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.3': - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3679,16 +4129,16 @@ packages: '@scure/starknet@1.1.0': resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} - '@simple-libs/child-process-utils@1.0.2': - resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} - engines: {node: '>=18'} + '@simple-libs/child-process-utils@2.0.0': + resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} + engines: {node: '>=22'} - '@simple-libs/stream-utils@1.2.0': - resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} - engines: {node: '>=18'} + '@simple-libs/stream-utils@2.0.0': + resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} + engines: {node: '>=22'} - '@simplewebauthn/browser@13.2.2': - resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==} + '@simplewebauthn/browser@13.3.0': + resolution: {integrity: sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==} '@sinclair/typebox@0.27.10': resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} @@ -3696,8 +4146,8 @@ packages: '@sinclair/typebox@0.33.22': resolution: {integrity: sha512-auUj4k+f4pyrIVf4GW5UKquSZFHJWri06QgARy9C0t9ZTjJLIuNIrr1yl9bWcJWJ1Gz1vOvYN1D+QPaIlNMVkQ==} - '@sinclair/typebox@0.34.49': - resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + '@sinclair/typebox@0.34.50': + resolution: {integrity: sha512-ydBWw0G6WFwWHzh9RK4B5c690UkreOG0llq0r+DaI7LgKgxigf8mhHzIPI3S0850g1BPkq/zpuCfrq4QFgUlTQ==} '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -3736,6 +4186,11 @@ packages: peerDependencies: '@solana/kit': ^2.1.0 + '@solana-program/system@0.10.0': + resolution: {integrity: sha512-Go+LOEZmqmNlfr+Gjy5ZWAdY5HbYzk2RBewD9QinEU/bBSzpFfzqDRT55JjFRBGJUvMgf3C2vfXEGT4i8DSI4g==} + peerDependencies: + '@solana/kit': ^5.0 + '@solana-program/system@0.7.0': resolution: {integrity: sha512-FKTBsKHpvHHNc1ATRm7SlC5nF/VdJtOSjldhcyfMN9R7xo712Mo2jHIzvBgn8zQO5Kg0DcWuKB7268Kv1ocicw==} peerDependencies: @@ -3752,17 +4207,25 @@ packages: peerDependencies: '@solana/kit': ^2.1.0 + '@solana-program/token@0.9.0': + resolution: {integrity: sha512-vnZxndd4ED4Fc56sw93cWZ2djEeeOFxtaPS8SPf5+a+JZjKA/EnKqzbE1y04FuMhIVrLERQ8uR8H2h72eZzlsA==} + peerDependencies: + '@solana/kit': ^5.0 + '@solana/accounts@2.3.0': resolution: {integrity: sha512-QgQTj404Z6PXNOyzaOpSzjgMOuGwG8vC66jSDB+3zHaRcEPRVRd2sVSrd1U6sHtnV3aiaS6YyDuPQMheg4K2jw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' - '@solana/accounts@3.0.3': - resolution: {integrity: sha512-KqlePrlZaHXfu8YQTCxN204ZuVm9o68CCcUr6l27MG2cuRUtEM1Ta0iR8JFkRUAEfZJC4Cu0ZDjK/v49loXjZQ==} + '@solana/accounts@5.5.1': + resolution: {integrity: sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/addresses@2.3.0': resolution: {integrity: sha512-ypTNkY2ZaRFpHLnHAgaW8a83N0/WoqdFvCqf4CQmnMdFsZSdC7qOwcbd7YzdaQn9dy+P2hybewzB+KP7LutxGA==} @@ -3770,11 +4233,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/addresses@3.0.3': - resolution: {integrity: sha512-AuMwKhJI89ANqiuJ/fawcwxNKkSeHH9CApZd2xelQQLS7X8uxAOovpcmEgiObQuiVP944s9ScGUT62Bdul9qYg==} + '@solana/addresses@5.5.1': + resolution: {integrity: sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/assertions@2.3.0': resolution: {integrity: sha512-Ekoet3khNg3XFLN7MIz8W31wPQISpKUGDGTylLptI+JjCDWx3PIa88xjEMqFo02WJ8sBj2NLV64Xg1sBcsHjZQ==} @@ -3782,11 +4248,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/assertions@3.0.3': - resolution: {integrity: sha512-2qspxdbWp2y62dfCIlqeWQr4g+hE8FYSSwcaP6itwMwGRb8393yDGCJfI/znuzJh6m/XVWhMHIgFgsBwnevCmg==} + '@solana/assertions@5.5.1': + resolution: {integrity: sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/buffer-layout@4.0.1': resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} @@ -3798,11 +4267,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/codecs-core@3.0.3': - resolution: {integrity: sha512-emKykJ3h1DmnDOY29Uv9eJXP8E/FHzvlUBJ6te+5EbKdFjj7vdlKYPfDxOI6iGdXTY+YC/ELtbNBh6QwF2uEDQ==} + '@solana/codecs-core@5.5.1': + resolution: {integrity: sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/codecs-data-structures@2.3.0': resolution: {integrity: sha512-qvU5LE5DqEdYMYgELRHv+HMOx73sSoV1ZZkwIrclwUmwTbTaH8QAJURBj0RhQ/zCne7VuLLOZFFGv6jGigWhSw==} @@ -3810,11 +4282,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/codecs-data-structures@3.0.3': - resolution: {integrity: sha512-R15cLp8riJvToXziW8lP6AMSwsztGhEnwgyGmll32Mo0Yjq+hduW2/fJrA/TJs6tA/OgTzMQjlxgk009EqZHCw==} + '@solana/codecs-data-structures@5.5.1': + resolution: {integrity: sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/codecs-numbers@2.3.0': resolution: {integrity: sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==} @@ -3822,11 +4297,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/codecs-numbers@3.0.3': - resolution: {integrity: sha512-pfXkH9J0glrM8qj6389GAn30+cJOxzXLR2FsPOHCUMXrqLhGjMMZAWhsQkpOQ37SGc/7EiQsT/gmyGC7gxHqJQ==} + '@solana/codecs-numbers@5.5.1': + resolution: {integrity: sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/codecs-strings@2.3.0': resolution: {integrity: sha512-y5pSBYwzVziXu521hh+VxqUtp0hYGTl1eWGoc1W+8mdvBdC1kTqm/X7aYQw33J42hw03JjryvYOvmGgk3Qz/Ug==} @@ -3835,12 +4313,17 @@ packages: fastestsmallesttextencoderdecoder: ^1.0.22 typescript: '>=5.3.3' - '@solana/codecs-strings@3.0.3': - resolution: {integrity: sha512-VHBXnnTVtcQ1j+7Vrz+qSYo38no+jiHRdGnhFspRXEHNJbllzwKqgBE7YN3qoIXH+MKxgJUcwO5KHmdzf8Wn2A==} + '@solana/codecs-strings@5.5.1': + resolution: {integrity: sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A==} engines: {node: '>=20.18.0'} peerDependencies: fastestsmallesttextencoderdecoder: ^1.0.22 - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + fastestsmallesttextencoderdecoder: + optional: true + typescript: + optional: true '@solana/codecs@2.3.0': resolution: {integrity: sha512-JVqGPkzoeyU262hJGdH64kNLH0M+Oew2CIPOa/9tR3++q2pEd4jU2Rxdfye9sd0Ce3XJrR5AIa8ZfbyQXzjh+g==} @@ -3848,11 +4331,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/codecs@3.0.3': - resolution: {integrity: sha512-GOHwTlIQsCoJx9Ryr6cEf0FHKAQ7pY4aO4xgncAftrv0lveTQ1rPP2inQ1QT0gJllsIa8nwbfXAADs9nNJxQDA==} + '@solana/codecs@5.5.1': + resolution: {integrity: sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/errors@2.3.0': resolution: {integrity: sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==} @@ -3861,12 +4347,15 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/errors@3.0.3': - resolution: {integrity: sha512-1l84xJlHNva6io62PcYfUamwWlc0eM95nHgCrKX0g0cLoC6D6QHYPCEbEVkR+C5UtP9JDgyQM8MFiv+Ei5tO9Q==} + '@solana/errors@5.5.1': + resolution: {integrity: sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg==} engines: {node: '>=20.18.0'} hasBin: true peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/fast-stable-stringify@2.3.0': resolution: {integrity: sha512-KfJPrMEieUg6D3hfQACoPy0ukrAV8Kio883llt/8chPEG3FVTX9z/Zuf4O01a15xZmBbmQ7toil2Dp0sxMJSxw==} @@ -3874,17 +4363,53 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/fast-stable-stringify@5.5.1': + resolution: {integrity: sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/functional@2.3.0': resolution: {integrity: sha512-AgsPh3W3tE+nK3eEw/W9qiSfTGwLYEvl0rWaxHht/lRcuDVwfKRzeSa5G79eioWFFqr+pTtoCr3D3OLkwKz02Q==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/functional@5.5.1': + resolution: {integrity: sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instruction-plans@5.5.1': + resolution: {integrity: sha512-7z3CB7YMcFKuVvgcnNY8bY6IsZ8LG61Iytbz7HpNVGX2u1RthOs1tRW8luTzSG1MPL0Ox7afyAVMYeFqSPHnaQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/instructions@2.3.0': resolution: {integrity: sha512-PLMsmaIKu7hEAzyElrk2T7JJx4D+9eRwebhFZpy2PXziNSmFF929eRHKUsKqBFM3cYR1Yy3m6roBZfA+bGE/oQ==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: '>=5.3.3' + + '@solana/instructions@5.5.1': + resolution: {integrity: sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/keys@2.3.0': resolution: {integrity: sha512-ZVVdga79pNH+2pVcm6fr2sWz9HTwfopDVhYb0Lh3dh+WBmJjwkabXEIHey2rUES7NjFa/G7sV8lrUn/v8LDCCQ==} @@ -3892,23 +4417,53 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/keys@5.5.1': + resolution: {integrity: sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/kit@2.3.0': resolution: {integrity: sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/kit@5.5.1': + resolution: {integrity: sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/nominal-types@2.3.0': resolution: {integrity: sha512-uKlMnlP4PWW5UTXlhKM8lcgIaNj8dvd8xO4Y9l+FVvh9RvW2TO0GwUO6JCo7JBzCB0PSqRJdWWaQ8pu1Ti/OkA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' - '@solana/nominal-types@3.0.3': - resolution: {integrity: sha512-aZavCiexeUAoMHRQg4s1AHkH3wscbOb70diyfjhwZVgFz1uUsFez7csPp9tNFkNolnadVb2gky7yBk3IImQJ6A==} + '@solana/nominal-types@5.5.1': + resolution: {integrity: sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/offchain-messages@5.5.1': + resolution: {integrity: sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/options@2.3.0': resolution: {integrity: sha512-PPnnZBRCWWoZQ11exPxf//DRzN2C6AoFsDI/u2AsQfYih434/7Kp4XLpfOMT/XESi+gdBMFNNfbES5zg3wAIkw==} @@ -3916,11 +4471,23 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/options@3.0.3': - resolution: {integrity: sha512-jarsmnQ63RN0JPC5j9sgUat07NrL9PC71XU7pUItd6LOHtu4+wJMio3l5mT0DHVfkfbFLL6iI6+QmXSVhTNF3g==} + '@solana/options@5.5.1': + resolution: {integrity: sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-core@5.5.1': + resolution: {integrity: sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/programs@2.3.0': resolution: {integrity: sha512-UXKujV71VCI5uPs+cFdwxybtHZAIZyQkqDiDnmK+DawtOO9mBn4Nimdb/6RjR2CXT78mzO9ZCZ3qfyX+ydcB7w==} @@ -3928,35 +4495,74 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/programs@5.5.1': + resolution: {integrity: sha512-7U9kn0Jsx1NuBLn5HRTFYh78MV4XN145Yc3WP/q5BlqAVNlMoU9coG5IUTJIG847TUqC1lRto3Dnpwm6T4YRpA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/promises@2.3.0': resolution: {integrity: sha512-GjVgutZKXVuojd9rWy1PuLnfcRfqsaCm7InCiZc8bqmJpoghlyluweNc7ml9Y5yQn1P2IOyzh9+p/77vIyNybQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/promises@5.5.1': + resolution: {integrity: sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-api@2.3.0': resolution: {integrity: sha512-UUdiRfWoyYhJL9PPvFeJr4aJ554ob2jXcpn4vKmRVn9ire0sCbpQKYx6K8eEKHZWXKrDW8IDspgTl0gT/aJWVg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-api@5.5.1': + resolution: {integrity: sha512-XWOQQPhKl06Vj0xi3RYHAc6oEQd8B82okYJ04K7N0Vvy3J4PN2cxeK7klwkjgavdcN9EVkYCChm2ADAtnztKnA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-parsed-types@2.3.0': resolution: {integrity: sha512-B5pHzyEIbBJf9KHej+zdr5ZNAdSvu7WLU2lOUPh81KHdHQs6dEb310LGxcpCc7HVE8IEdO20AbckewDiAN6OCg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-parsed-types@5.5.1': + resolution: {integrity: sha512-HEi3G2nZqGEsa3vX6U0FrXLaqnUCg4SKIUrOe8CezD+cSFbRTOn3rCLrUmJrhVyXlHoQVaRO9mmeovk31jWxJg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-spec-types@2.3.0': resolution: {integrity: sha512-xQsb65lahjr8Wc9dMtP7xa0ZmDS8dOE2ncYjlvfyw/h4mpdXTUdrSMi6RtFwX33/rGuztQ7Hwaid5xLNSLvsFQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' - '@solana/rpc-spec-types@3.0.3': - resolution: {integrity: sha512-A6Jt8SRRetnN3CeGAvGJxigA9zYRslGgWcSjueAZGvPX+MesFxEUjSWZCfl+FogVFvwkqfkgQZQbPAGZQFJQ6Q==} + '@solana/rpc-spec-types@5.5.1': + resolution: {integrity: sha512-6OFKtRpIEJQs8Jb2C4OO8KyP2h2Hy1MFhatMAoXA+0Ik8S3H+CicIuMZvGZ91mIu/tXicuOOsNNLu3HAkrakrw==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/rpc-spec@2.3.0': resolution: {integrity: sha512-fA2LMX4BMixCrNB2n6T83AvjZ3oUQTu7qyPLyt8gHQaoEAXs8k6GZmu6iYcr+FboQCjUmRPgMaABbcr9j2J9Sw==} @@ -3964,11 +4570,14 @@ packages: peerDependencies: typescript: '>=5.3.3' - '@solana/rpc-spec@3.0.3': - resolution: {integrity: sha512-MZn5/8BebB6MQ4Gstw6zyfWsFAZYAyLzMK+AUf/rSfT8tPmWiJ/mcxnxqOXvFup/l6D67U8pyGpIoFqwCeZqqA==} + '@solana/rpc-spec@5.5.1': + resolution: {integrity: sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/rpc-subscriptions-api@2.3.0': resolution: {integrity: sha512-9mCjVbum2Hg9KGX3LKsrI5Xs0KX390lS+Z8qB80bxhar6MJPugqIPH8uRgLhCW9GN3JprAfjRNl7our8CPvsPQ==} @@ -3976,6 +4585,15 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-subscriptions-api@5.5.1': + resolution: {integrity: sha512-5Oi7k+GdeS8xR2ly1iuSFkAv6CZqwG0Z6b1QZKbEgxadE1XGSDrhM2cn59l+bqCozUWCqh4c/A2znU/qQjROlw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-subscriptions-channel-websocket@2.3.0': resolution: {integrity: sha512-2oL6ceFwejIgeWzbNiUHI2tZZnaOxNTSerszcin7wYQwijxtpVgUHiuItM/Y70DQmH9sKhmikQp+dqeGalaJxw==} engines: {node: '>=20.18.0'} @@ -3983,41 +4601,89 @@ packages: typescript: '>=5.3.3' ws: ^8.18.0 + '@solana/rpc-subscriptions-channel-websocket@5.5.1': + resolution: {integrity: sha512-7tGfBBrYY8TrngOyxSHoCU5shy86iA9SRMRrPSyBhEaZRAk6dnbdpmUTez7gtdVo0BCvh9nzQtUycKWSS7PnFQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-subscriptions-spec@2.3.0': resolution: {integrity: sha512-rdmVcl4PvNKQeA2l8DorIeALCgJEMSu7U8AXJS1PICeb2lQuMeaR+6cs/iowjvIB0lMVjYN2sFf6Q3dJPu6wWg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-subscriptions-spec@5.5.1': + resolution: {integrity: sha512-iq+rGq5fMKP3/mKHPNB6MC8IbVW41KGZg83Us/+LE3AWOTWV1WT20KT2iH1F1ik9roi42COv/TpoZZvhKj45XQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-subscriptions@2.3.0': resolution: {integrity: sha512-Uyr10nZKGVzvCOqwCZgwYrzuoDyUdwtgQRefh13pXIrdo4wYjVmoLykH49Omt6abwStB0a4UL5gX9V4mFdDJZg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-subscriptions@5.5.1': + resolution: {integrity: sha512-CTMy5bt/6mDh4tc6vUJms9EcuZj3xvK0/xq8IQ90rhkpYvate91RjBP+egvjgSayUg9yucU9vNuUpEjz4spM7w==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-transformers@2.3.0': resolution: {integrity: sha512-UuHYK3XEpo9nMXdjyGKkPCOr7WsZsxs7zLYDO1A5ELH3P3JoehvrDegYRAGzBS2VKsfApZ86ZpJToP0K3PhmMA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-transformers@5.5.1': + resolution: {integrity: sha512-OsWqLCQdcrRJKvHiMmwFhp9noNZ4FARuMkHT5us3ustDLXaxOjF0gfqZLnMkulSLcKt7TGXqMhBV+HCo7z5M8Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-transport-http@2.3.0': resolution: {integrity: sha512-HFKydmxGw8nAF5N+S0NLnPBDCe5oMDtI2RAmW8DMqP4U3Zxt2XWhvV1SNkAldT5tF0U1vP+is6fHxyhk4xqEvg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/rpc-transport-http@5.5.1': + resolution: {integrity: sha512-yv8GoVSHqEV0kUJEIhkdOVkR2SvJ6yoWC51cJn2rSV7plr6huLGe0JgujCmB7uZhhaLbcbP3zxXxu9sOjsi7Fg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/rpc-types@2.3.0': resolution: {integrity: sha512-O09YX2hED2QUyGxrMOxQ9GzH1LlEwwZWu69QbL4oYmIf6P5dzEEHcqRY6L1LsDVqc/dzAdEs/E1FaPrcIaIIPw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' - '@solana/rpc-types@3.0.3': - resolution: {integrity: sha512-petWQ5xSny9UfmC3Qp2owyhNU0w9SyBww4+v7tSVyXMcCC9v6j/XsqTeimH1S0qQUllnv0/FY83ohFaxofmZ6Q==} + '@solana/rpc-types@5.5.1': + resolution: {integrity: sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/rpc@2.3.0': resolution: {integrity: sha512-ZWN76iNQAOCpYC7yKfb3UNLIMZf603JckLKOOLTHuy9MZnTN8XV6uwvDFhf42XvhglgUjGCEnbUqWtxQ9pa/pQ==} @@ -4025,29 +4691,59 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/rpc@5.5.1': + resolution: {integrity: sha512-ku8zTUMrkCWci66PRIBC+1mXepEnZH/q1f3ck0kJZ95a06bOTl5KU7HeXWtskkyefzARJ5zvCs54AD5nxjQJ+A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/signers@2.3.0': resolution: {integrity: sha512-OSv6fGr/MFRx6J+ZChQMRqKNPGGmdjkqarKkRzkwmv7v8quWsIRnJT5EV8tBy3LI4DLO/A8vKiNSPzvm1TdaiQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/signers@5.5.1': + resolution: {integrity: sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/subscribable@2.3.0': resolution: {integrity: sha512-DkgohEDbMkdTWiKAoatY02Njr56WXx9e/dKKfmne8/Ad6/2llUIrax78nCdlvZW9quXMaXPTxZvdQqo9N669Og==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/subscribable@5.5.1': + resolution: {integrity: sha512-9K0PsynFq0CsmK1CDi5Y2vUIJpCqkgSS5yfDN0eKPgHqEptLEaia09Kaxc90cSZDZU5mKY/zv1NBmB6Aro9zQQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/sysvars@2.3.0': resolution: {integrity: sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' - '@solana/sysvars@3.0.3': - resolution: {integrity: sha512-GnHew+QeKCs2f9ow+20swEJMH4mDfJA/QhtPgOPTYQx/z69J4IieYJ7fZenSHnA//lJ45fVdNdmy1trypvPLBQ==} + '@solana/sysvars@5.5.1': + resolution: {integrity: sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA==} engines: {node: '>=20.18.0'} peerDependencies: - typescript: '>=5.3.3' + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true '@solana/transaction-confirmation@2.3.0': resolution: {integrity: sha512-UiEuiHCfAAZEKdfne/XljFNJbsKAe701UQHKXEInYzIgBjRbvaeYZlBmkkqtxwcasgBTOmEaEKT44J14N9VZDw==} @@ -4055,18 +4751,45 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/transaction-confirmation@5.5.1': + resolution: {integrity: sha512-j4mKlYPHEyu+OD7MBt3jRoX4ScFgkhZC6H65on4Fux6LMScgivPJlwnKoZMnsgxFgWds0pl+BYzSiALDsXlYtw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/transaction-messages@2.3.0': resolution: {integrity: sha512-bgqvWuy3MqKS5JdNLH649q+ngiyOu5rGS3DizSnWwYUd76RxZl1kN6CoqHSrrMzFMvis6sck/yPGG3wqrMlAww==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/transaction-messages@5.5.1': + resolution: {integrity: sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/transactions@2.3.0': resolution: {integrity: sha512-LnTvdi8QnrQtuEZor5Msje61sDpPstTVwKg4y81tNxDhiyomjuvnSNLAq6QsB9gIxUqbNzPZgOG9IU4I4/Uaug==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.3.3' + '@solana/transactions@5.5.1': + resolution: {integrity: sha512-8hHtDxtqalZ157pnx6p8k10D7J/KY/biLzfgh9R09VNLLY3Fqi7kJvJCr7M2ik3oRll56pxhraAGCC9yIT6eOA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/wallet-adapter-alpha@0.1.14': resolution: {integrity: sha512-ZSEvQmTdkiXPeHWIHbvdU4yDC5PfyTqG/1ZKIf2Uo6c+HslMkYer7mf9HUqJJ80dU68XqBbzBlIv34LCDVWijw==} engines: {node: '>=20'} @@ -4199,13 +4922,6 @@ packages: peerDependencies: '@solana/web3.js': ^1.98.0 - '@solana/wallet-adapter-react@0.15.39': - resolution: {integrity: sha512-WXtlo88ith5m22qB+qiGw301/Zb9r5pYr4QdXWmlXnRNqwST5MGmJWhG+/RVrzc+OG7kSb3z1gkVNv+2X/Y0Gg==} - engines: {node: '>=20'} - peerDependencies: - '@solana/web3.js': ^1.98.0 - react: '*' - '@solana/wallet-adapter-safepal@0.5.22': resolution: {integrity: sha512-K1LlQIPoKgg3rdDIVUtMV218+uUM1kCtmuVKq2N+e+ZC8zK05cW3w7++nakDtU97AOmg+y4nsSFRCFsWBWmhTw==} engines: {node: '>=20'} @@ -4382,6 +5098,7 @@ packages: '@stellar/stellar-base@14.1.0': resolution: {integrity: sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==} engines: {node: '>=20.0.0'} + deprecated: This package is now rolled into @stellar/stellar-sdk. Please use @stellar/stellar-sdk to continue receiving updates and support. '@stellar/stellar-sdk@14.2.0': resolution: {integrity: sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==} @@ -4393,22 +5110,22 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@tanstack/query-core@5.100.10': - resolution: {integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==} + '@tanstack/query-core@5.101.2': + resolution: {integrity: sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==} - '@tanstack/react-query@5.100.10': - resolution: {integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==} + '@tanstack/react-query@5.101.2': + resolution: {integrity: sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==} peerDependencies: react: ^18 || ^19 - '@tanstack/react-virtual@3.13.24': - resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} + '@tanstack/react-virtual@3.14.5': + resolution: {integrity: sha512-4EKRXh7zBLkbKbFmG3AUVkircuHd+7OdT1pocJSepxtfBd3qnrJgJ5rtPkRYyo9fmyVb2+pI2xPy5oYvMLQy6A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/virtual-core@3.14.0': - resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} + '@tanstack/virtual-core@3.17.3': + resolution: {integrity: sha512-8Np/TFELpI0ySuJoVmjvOrQYXH/8sTX0Biv9szhFhY39xOdAAY+smrMxjxOum/ux3eM8MUJQsEJ0/R0UpvC8dw==} '@testing-library/jest-dom@5.17.0': resolution: {integrity: sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==} @@ -4609,62 +5326,62 @@ packages: peerDependencies: tslib: ^2.6.2 - '@tronweb3/tronwallet-abstract-adapter@1.1.13': - resolution: {integrity: sha512-Sv1FUo5A1fNgc2oZ2LMJ7SmA5TpW5MSWTNjnWJ8t6Z+aRfLbc/s8nkPPLqm0YfR+Sa80sLhMfqJsJzYf73ynMg==} + '@tronweb3/tronwallet-abstract-adapter@1.2.0': + resolution: {integrity: sha512-Xy3ftKnxhEEWOWG6OXoN877t4oTr4W/kuQb7LWVYdn5PNeQ90LXj7eaTu9e6624iozDRuxMix5FThtqEt5F7dA==} engines: {node: '>=16', pnpm: '>=7'} - '@tronweb3/tronwallet-adapter-bitkeep@1.1.8': - resolution: {integrity: sha512-TqCfYTBhPMm9kW6GW9gsxNiyuFeqdkvvyDjfGD8L2o4KExNKOSuGOCU/Qgwo0imwtmPg8LAWZzMBWxQGfGugIQ==} + '@tronweb3/tronwallet-adapter-bitkeep@1.2.0': + resolution: {integrity: sha512-icWWcQmlTv/k/UXYZSjimzUq0iI1M7bmuhJoaUa70zkWAnzllwNQVPlEuK2Q+2jIKpMQyYJbmHwC+2YV7lMIog==} engines: {node: '>=16', pnpm: '>=7'} - '@tronweb3/tronwallet-adapter-ledger@1.1.13': - resolution: {integrity: sha512-pMjO9dxIFA53WsTbUTXA4NPocST4qXXDvpXx7WMTvm1ingBliOH5Jn2YwKFQ6B1dbcU/hBXWNGO6dN/K9ATmyQ==} + '@tronweb3/tronwallet-adapter-ledger@1.1.14': + resolution: {integrity: sha512-Pc0/OpGLWNIfC9BLcsWqwVSRhL0o+v88WP8igb9h+DgSKZ1IV34Fw3ZxV19IOQhCZir7rbB3CEsay7IAapqbow==} engines: {node: '>=16', pnpm: '>=7'} - '@tronweb3/tronwallet-adapter-tronlink@1.1.16': - resolution: {integrity: sha512-PqwiRQdcZ3y+YiVX2kPcmLZtHDD9IDw/9oTjAyla/jFkaaEpMDpPRV4S6yulDyHzjVOJD9yOan9EXtr0KU+aKg==} + '@tronweb3/tronwallet-adapter-tronlink@1.2.0': + resolution: {integrity: sha512-utEIJJ4lBjy7waTgohwoeaD8pM5AfQvXKZWabZP5YPiorNq//TseEr4Vy80uNco/SD1PhXpNjMYSwuCo+15J7A==} engines: {node: '>=16', pnpm: '>=7'} - '@tronweb3/tronwallet-adapter-walletconnect@3.0.3': - resolution: {integrity: sha512-ePLs2LMgLkhi0ldXyK2ClxN34N9CeOG8YxgERnAm7tVj30XQEXk4S6yn1QHXHfyU1SJSGaveVl0Mp7lCiVwyhA==} - engines: {node: '>=16', pnpm: '>=7'} + '@tronweb3/tronwallet-adapter-walletconnect@3.0.5': + resolution: {integrity: sha512-k9kVk4A5f7fmJPgbp+6Vs8SZNEg8bXIw5XsdIOXxPV9cReuge6/vsN0uDW2elk0VRDGX8rcE7+Fagf1lxNF8hg==} + engines: {node: '>=20.18.0', pnpm: '>=7'} - '@tronweb3/walletconnect-tron@4.0.2': - resolution: {integrity: sha512-ubJHqCYxeS3obAxIdWkGOi2kWfX8CNbWHBmAj9LEiTwB4BAgdBJ3P7T2TwSSIZa6ARvc7zmbFtarFCQ4ckWAKQ==} + '@tronweb3/walletconnect-tron@4.0.3': + resolution: {integrity: sha512-P3K/xUsP/7GuVTpKi0z/NLF9cln0MCQPnaI4lM9xZUTutAwuBHli2jwkXJ8VRsslynYcOXPNnMalq9Kt5PruDw==} engines: {node: '>=18', pnpm: '>=7'} - '@turbo/darwin-64@2.9.18': - resolution: {integrity: sha512-9f27peFu16ur8c0v9nUFUEyBnbKuuFsUTjHFWfmwGfzySBXbHwzU44QhZon6Mznz0cHsIr3984NQj/bVrnGSRw==} + '@turbo/darwin-64@2.10.4': + resolution: {integrity: sha512-m1MUEI4MJ69r5CwfMYxmHi0H0rrgiYCBOp0tgBZ9x/YVvOb5uu/lRIDyDwdtH054R2yWeQaIigUGu6aCX9f8cA==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.18': - resolution: {integrity: sha512-9A6TMRq/Ib+QnbhLlgkhOm+624wO4pzSQ/yQviQfWHOlFvaYxdnIAYmu2H6TS6y7kSVL0DvzNe04NbESTOzFVQ==} + '@turbo/darwin-arm64@2.10.4': + resolution: {integrity: sha512-VQ1Yxs5zkPT+2z7t1P4mvn6JmcKLkOCAsPuK9XbOvuVj0DlTlETfIXNisX0771v/vTWHOQqiwoGi+TtAUq8efw==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.9.18': - resolution: {integrity: sha512-zCdIDtz69AnbYh913elJRRoF3QY5aa2HNnf+4rAkc7bQ+tWujiDkCNV7stazOUPggaDvhKIf2Z87qHftTeXSkw==} + '@turbo/linux-64@2.10.4': + resolution: {integrity: sha512-IzV1QovmwX7mfGnVinmE++2IB8tbeo38weltiuH5zNqwCTBjLs/DytyRKx+bmnhHdXIq9SheR8p0Nip/LBUPHg==} cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.18': - resolution: {integrity: sha512-Va1kXI04naMgYwqv/5Dfa36dTDx8015U7oaQAjrXa45ua9OoFjSV4OmvkML4EmXvUclQHCiBRbY8bvd0jV7eAg==} + '@turbo/linux-arm64@2.10.4': + resolution: {integrity: sha512-rfujSQkP5aYiRn0PgTM7F00WkJCP/bKDVZbOx3WmrZwa/vHA0bplhCl328kpX7VI9HH2vI90ISGwuSVgJgoqTw==} cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.18': - resolution: {integrity: sha512-m0kDhZANxSNz9ck1ybogFscHabriAsp4eDFNrN/1H5WrgTF7b3VlcPZnhuO3v2+E2KnCbeAc+UUT10BZZHdDKw==} + '@turbo/windows-64@2.10.4': + resolution: {integrity: sha512-NnspP7Wd5fa3Wwnqv9bKfhegqZzuHBgbPxdZU/idTLQcazx/vgKu95JlCx2YHY0hdvKCnPcARrDwM+KEUmaO7A==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.18': - resolution: {integrity: sha512-nUdR8WqoomUys9iIQmG45TMiizJ+5BV8egSeLLZba/AWblyp3fVBcIH1kSE58OtK4g2YzbMJEth6Ttv9w5rqMA==} + '@turbo/windows-arm64@2.10.4': + resolution: {integrity: sha512-Iv02YgOpaEShc2OkG7mgCJ2pEw1RUKiKbs0h8W5wAf4jZ5vpmraTEjuGTgHRuOORQnC1GN3KHo5WB+hu1abRMA==} cpu: [arm64] os: [win32] - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -4693,29 +5410,29 @@ packages: '@types/css-font-loading-module@0.0.7': resolution: {integrity: sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==} - '@types/d3-array@3.0.3': - resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - '@types/d3-color@3.1.0': - resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} '@types/d3-ease@3.0.2': resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - '@types/d3-interpolate@3.0.1': - resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} '@types/d3-path@3.1.1': resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - '@types/d3-scale@4.0.2': - resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} '@types/d3-shape@3.1.8': resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} - '@types/d3-time@3.0.0': - resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} @@ -4750,9 +5467,6 @@ packages: '@types/json-logic-js@2.0.5': resolution: {integrity: sha512-hu/FTi0zwCjQEFfbPur275cNoZj6NsuOBhhYNzqoSdfmMhuxFr58OZ957lyIOWc9+kO+2tFlBthRjcxuytD4HA==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash.merge@4.6.9': resolution: {integrity: sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==} @@ -4777,11 +5491,11 @@ packages: '@types/node@22.7.5': resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} - '@types/node@25.7.0': - resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==} + '@types/node@26.1.0': + resolution: {integrity: sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==} - '@types/node@26.0.0': - resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} @@ -4830,17 +5544,141 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@typescript/typescript6@6.0.2': + resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==} + hasBin: true + '@vanilla-extract/babel-plugin-debug-ids@1.2.2': resolution: {integrity: sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw==} - '@vanilla-extract/compiler@0.7.0': - resolution: {integrity: sha512-rZQ40HVmsxfGLjoflwwsaUBLfpbpKDoZC19oiDA0FHq4LdrYtyVbFkc0MfqkNo/qBCvaZfsRezCqk0QQxCqZ8w==} + '@vanilla-extract/compiler@0.7.1': + resolution: {integrity: sha512-mOokbA2k1Lx3BO1/Qa9rpSWiIWeOHBt4aRHX0e5WMDu53//ifojeZJAvdXj/YkZI8z2AruxXYaLX6YlL8jfzyQ==} '@vanilla-extract/css@1.17.3': resolution: {integrity: sha512-jHivr1UPoJTX5Uel4AZSOwrCf4mO42LcdmnhJtUxZaRWhW4FviFbIfs0moAWWld7GOT+2XnuVZjjA/K32uUnMQ==} - '@vanilla-extract/css@1.20.1': - resolution: {integrity: sha512-5I9RNo5uZW9tsBnqrWzJqELegOqTHBrZyDFnES0gR9gJJHBB9dom1N0bwITM9tKwBcfKrTX4a6DHVeQdJ2ubQA==} + '@vanilla-extract/css@1.21.1': + resolution: {integrity: sha512-T6z6oeT+o0OwqukE6kLs9w5ooB75b8NPRadyU9pPTml83l40TPN+mscPt2OQ9P9cYfwEBMfsfivwiJujs2mzmg==} '@vanilla-extract/dynamic@2.1.4': resolution: {integrity: sha512-7+Ot7VlP3cIzhJnTsY/kBtNs21s0YD7WI1rKJJKYP56BkbDxi/wrQUWMGEczKPUDkJuFcvbye+E2ub1u/mHH9w==} @@ -4864,18 +5702,18 @@ packages: peerDependencies: '@vanilla-extract/css': ^1.0.0 - '@vanilla-extract/sprinkles@1.6.5': - resolution: {integrity: sha512-HOYidLONR/SeGk8NBAeI64I4gYdsMX9vJmniL13ZcLVwawyK0s2GUENEAcGA+GYLIoeyQB61UqmhqPodJry7zA==} + '@vanilla-extract/sprinkles@1.7.0': + resolution: {integrity: sha512-7I8rt3m1OIEMJRtpMTnPwMy+C1DHpgvenNF1P7lgFFXGzxAoW57+Qk439cJFqONaqsLsTiYOAOVnP4QigCWa1A==} peerDependencies: '@vanilla-extract/css': ^1.0.0 - '@vanilla-extract/vite-plugin@5.2.2': - resolution: {integrity: sha512-AUyB4fDR2b/Mo0lcXhhlf6RxnDPYwFMyKKopalJ4BwQNKYzZSoTwHJ1PLPO9SKhpz7lzXc0Z18GHQZOewzl3YA==} + '@vanilla-extract/vite-plugin@5.2.4': + resolution: {integrity: sha512-KXm+Hghpr7/BNIPirsSVD7otMDCwjj9vfgc02CmtYoJ5t4shxQU0QbCRHc9YxlVvAeGvZfE2TmGkvhw2N/L/aw==} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@vitejs/plugin-react@6.0.2': - resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + '@vitejs/plugin-react@6.0.3': + resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 @@ -4887,22 +5725,22 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/browser-playwright@4.1.9': - resolution: {integrity: sha512-Bq1rOGf9waevzG3EOkO/dene6bvKTUsZMVg8S1i+WH3JcMjuXEjiahP9rAqZRELUqjBySOJsvvSWqK/B3wjKQw==} + '@vitest/browser-playwright@4.1.10': + resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==} peerDependencies: playwright: '*' - vitest: 4.1.9 + vitest: 4.1.10 - '@vitest/browser@4.1.9': - resolution: {integrity: sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==} + '@vitest/browser@4.1.10': + resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==} peerDependencies: - vitest: 4.1.9 + vitest: 4.1.10 - '@vitest/expect@4.1.9': - resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/mocker@4.1.9': - resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -4912,34 +5750,34 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.9': - resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/runner@4.1.9': - resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@4.1.9': - resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@4.1.9': - resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@4.1.9': - resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - '@wagmi/connectors@8.0.13': - resolution: {integrity: sha512-uRWutZygoX5K1Vk4svKeMxmyeTEjdZn8f/+EDKQEQaFMvy6yMVlLpy5DKFK0NRjtTIinRrqVccDqJQYCcC62hw==} + '@wagmi/connectors@8.0.22': + resolution: {integrity: sha512-91fGrjf4ZOjd94DtN/p8/6FbPe5xA6j5camPiUkIoS30HqHqz00dYCsBX+dQ1DMFpHaGesgdpb9tz9AElS0Bxw==} peerDependencies: '@base-org/account': ^2.5.1 '@coinbase/wallet-sdk': ^4.3.6 - '@metamask/connect-evm': ^1.0.0 + '@metamask/connect-evm': ^2.1.0 '@safe-global/safe-apps-provider': ~0.18.6 '@safe-global/safe-apps-sdk': ^9.1.0 - '@wagmi/core': 3.4.11 + '@wagmi/core': 3.6.1 '@walletconnect/ethereum-provider': ^2.21.1 - accounts: ~0.10 + accounts: ~0.14 porto: ~0.2.35 - typescript: '>=5.7.3' + typescript: '>=5.9.3' viem: 2.x peerDependenciesMeta: '@base-org/account': @@ -4961,12 +5799,12 @@ packages: typescript: optional: true - '@wagmi/core@3.4.11': - resolution: {integrity: sha512-dmrZr0fKxCmG3pyWO9//oYtE5cgWRrgaQjC0+jX6IiuufE4MSW7gSL2MKdK8bvT3jg4Ra8cdRIJpveHJSHKdNw==} + '@wagmi/core@3.6.1': + resolution: {integrity: sha512-lmMLIBq0V+WM98ypeG6f4skowPlyuf8n+bWCD9SVJw3tww5sZL7Ab9rq2Js9+Yif1w9OKPy7yLWboIRT/EhRIA==} peerDependencies: '@tanstack/query-core': '>=5.0.0' - accounts: ~0.8.1 - typescript: '>=5.7.3' + accounts: ~0.14 + typescript: '>=5.9.3' viem: 2.x peerDependenciesMeta: '@tanstack/query-core': @@ -5021,9 +5859,9 @@ packages: resolution: {integrity: sha512-XtwPUCj3bCNX/2yjYGlQyvcsn32wkzixCjyWOD4pdKFVk7opZPAdF4xr85rmo6nJ7AiBYxjV1IH0bemTPEdE6Q==} engines: {node: '>=18'} - '@walletconnect/core@2.21.7': - resolution: {integrity: sha512-q/Au5Ne3g4R+q4GvHR5cvRd3+ha00QZCZiCs058lmy+eDbiZd0YsautvTPJ5a2guD6UaS1k/w5e1JHgixdcgLA==} - engines: {node: '>=18'} + '@walletconnect/core@2.23.7': + resolution: {integrity: sha512-yTyymn9mFaDZkUfLfZ3E9VyaSDPeHAXlrPxQRmNx2zFsEt/25GmTU2A848aomimLxZnAG2jNLhxbJ8I0gyNV+w==} + engines: {node: '>=18.20.8'} '@walletconnect/core@2.23.9': resolution: {integrity: sha512-ws4WG8LeagUo2ERRo02HryXRcpwIRmCQ3pHLW5gWbVReLXXIpgk6ZAfID3fEGHevIwwnHSGww+nNhNpdXyiq0g==} @@ -5119,9 +5957,8 @@ packages: resolution: {integrity: sha512-v1OJ9IQCZAqaDEUYGFnGLe2fSp8DN9cv7j8tUYm5ngiFK7h6LjP4Ew3gGCca4AHWzMFkHuIRNQ+6Ypep1K/B7g==} deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' - '@walletconnect/sign-client@2.21.7': - resolution: {integrity: sha512-9k/JEl9copR6nXRhqnmzWz2Zk1hiWysH+o6bp6Cqo8TgDUrZoMLBZMZ6qbo+2HLI54V02kKf0Vg8M81nNFOpjQ==} - deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + '@walletconnect/sign-client@2.23.7': + resolution: {integrity: sha512-SX61lzb1bTl/LijlcHQttnoHPBzzoY5mW9ArR6qhFtDNDTS7yr2rcH7rCngxHlYeb4rAYcWLHgbiGSrdKxl/mg==} '@walletconnect/sign-client@2.23.9': resolution: {integrity: sha512-Xj+hw4E6mGRyhCdVOT/RMgnG+up/Y3v0ho5PlkVozvXWeVSqHNh9DmjLuU97a7OACoGd/oHBF6g3NVqD7MgCMQ==} @@ -5153,8 +5990,8 @@ packages: '@walletconnect/types@2.21.4': resolution: {integrity: sha512-6O61esDSW8FZNdFezgB4bX2S35HM6tCwBEjGHNB8JeoKCfpXG33hw2raU/SBgBL/jmM57QRW4M1aFH7v1u9z7g==} - '@walletconnect/types@2.21.7': - resolution: {integrity: sha512-kyGnFje4Iq+XGkZZcSoAIrJWBE4BeghVW4O7n9e1MhUyeOOtO55M/kcqceNGYrvwjHvdN+Kf+aoLnKC0zKlpbQ==} + '@walletconnect/types@2.23.7': + resolution: {integrity: sha512-6PAKK+iR2IntmlkCFLMAHjYeIaerCJJYRDmdRimhon0u+aNmQT+HyGM6zxDAth0rdpBD7qEvKP5IXZTE7KFUhw==} '@walletconnect/types@2.23.9': resolution: {integrity: sha512-IUl1PpD/Dig8IE2OZ9XtjbPohEyOZJ73xs92EDUzoIyzRtfm36g2D340pY3iu3AAdLv1yFiaZafB8Hf8RFze8A==} @@ -5179,9 +6016,11 @@ packages: resolution: {integrity: sha512-ZSYU5H7Zi/nEy3L21kw5l3ovMagrbXDRKBG8vjPpGQAkflQocRj6d0SesFOCBEdJS16nt+6dKI2f5blpOGzyTQ==} deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' - '@walletconnect/universal-provider@2.21.7': - resolution: {integrity: sha512-8PB+vA5VuR9PBqt5Y0xj4JC2doYNPlXLGQt3wJORVF9QC227Mm/8R1CAKpmneeLrUH02LkSRwx+wnN/pPnDiQA==} - deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + '@walletconnect/universal-provider@2.23.7': + resolution: {integrity: sha512-6UicU/Mhr/1bh7MNoajypz7BhigORbHpP1LFTf8FYLQGDqzmqHMqmMH2GDAImtaY2sFTi2jBvc22tLl8VMze/A==} + + '@walletconnect/universal-provider@2.23.9': + resolution: {integrity: sha512-dNk6X1USUcIX1nx3H61V3pO15E/2ejyeBsKLBOo8YXrnYCrKGG/KB1LIqJXHpQlXT+9bJE9cOnn61ETdCXgkPw==} '@walletconnect/utils@2.19.0': resolution: {integrity: sha512-LZ0D8kevknKfrfA0Sq3Hf3PpmM8oWyNfsyWwFR51t//2LBgtN2Amz5xyoDDJcjLibIbKAxpuo/i0JYAQxz+aPA==} @@ -5198,8 +6037,8 @@ packages: '@walletconnect/utils@2.21.4': resolution: {integrity: sha512-LuSyBXvRLiDqIu4uMFei5eJ/WPhkIkdW58fmDlRnryatIuBPCho3dlrNSbOjVSdsID+OvxjOlpPLi+5h4oTbaA==} - '@walletconnect/utils@2.21.7': - resolution: {integrity: sha512-qyaclTgcFf9AwVuoV8CLLg8wfH3nX7yZdpylNkDqCpS7wawQL9zmFFTaGgma8sQrCsd3Sd9jUIymcpRvCJnSTw==} + '@walletconnect/utils@2.23.7': + resolution: {integrity: sha512-3p38gNrkVcIiQixVrlsWSa66Gjs5PqHOug2TxDgYUVBW5NcKjwQA08GkC6CKBQUfr5iaCtbfy6uZJW1LKSIvWQ==} '@walletconnect/utils@2.23.9': resolution: {integrity: sha512-C5TltCs8UPypNiteYnKSv8+ZDK2EjVDyXCxN6kA9bkA+j6KGsNIV7l9MUA8WBAvE5Gi5EcBdhD3R9Hpo/1HHqQ==} @@ -5220,26 +6059,6 @@ packages: '@xstate/fsm@1.6.5': resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==} - '@xstate/react@6.1.0': - resolution: {integrity: sha512-ep9F0jGTI63B/jE8GHdMpUqtuz7yRebNaKv8EMUaiSi29NOglywc2X2YSOV/ygbIK+LtmgZ0q9anoEA2iBSEOw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - xstate: ^5.28.0 - peerDependenciesMeta: - xstate: - optional: true - - '@xstate/store@3.17.5': - resolution: {integrity: sha512-ITajQVRAMa1pgmgM0OdRFYQ6OQ8GK4ynh391p13GKtZnELXn9Hgs9pr4mqO1Z95HH9INcmczmWi5xk3O18GBWg==} - peerDependencies: - react: ^18.2.0 || ^19.0.0 - solid-js: ^1.7.6 - peerDependenciesMeta: - react: - optional: true - solid-js: - optional: true - '@zeit/schemas@2.36.0': resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} @@ -5256,6 +6075,17 @@ packages: resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} hasBin: true + abitype@1.0.6: + resolution: {integrity: sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + abitype@1.0.8: resolution: {integrity: sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==} peerDependencies: @@ -5293,18 +6123,13 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - abort-error@1.0.1: - resolution: {integrity: sha512-fxqCblJiIPdSXIUrxI0PL+eJG49QdP9SQ70qtB65MVAoMr2rASlOyAbJFOylfB467F/f+5BCLJJq58RYi7mGfg==} + abort-error@1.0.2: + resolution: {integrity: sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==} accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.17.0: resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} @@ -5313,6 +6138,10 @@ packages: aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -5372,6 +6201,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argue-cli@3.1.0: + resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} + engines: {node: '>=22'} + aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -5380,9 +6213,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -5413,8 +6243,8 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + autoprefixer@10.5.2: + resolution: {integrity: sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -5424,14 +6254,22 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + axios-retry@4.5.0: + resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==} + peerDependencies: + axios: 0.x || 1.x + axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} axios@1.16.0: resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} - b4a@1.7.3: - resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -5499,13 +6337,8 @@ packages: resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} engines: {node: '>=6.0.0'} - baseline-browser-mapping@2.10.29: - resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - baseline-browser-mapping@2.10.38: - resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} + baseline-browser-mapping@2.10.41: + resolution: {integrity: sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==} engines: {node: '>=6.0.0'} hasBin: true @@ -5532,8 +6365,8 @@ packages: big.js@6.2.2: resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} - bignumber.js@11.1.1: - resolution: {integrity: sha512-LNkCYMieJqAts/Sj5094B92c5q+yRPlXWpABJnHMbUjB/F8AUjELmSmYX5mxbNiY/QnGnJvJIrnRuW5gUqbW5Q==} + bignumber.js@11.1.5: + resolution: {integrity: sha512-6WmzCNtUnfKpbozq+hOgWaZMMzORmYBwF1xZScyoIX3QRYWeKTtxxwDOW5tIz7C9BdjkIYHGTcelCLkXg0mndw==} bignumber.js@9.1.2: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} @@ -5573,9 +6406,15 @@ packages: bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} + bn.js@4.12.4: + resolution: {integrity: sha512-njR1b+ixG2ufvL9Zn9JGneW+b5GV6jqpYyPPpg4QVt723b5kJPGUczkUyWEH9BwEA74UakJZ43I4FDLBF7ci0g==} + bn.js@5.2.3: resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} + bn.js@5.2.4: + resolution: {integrity: sha512-QL7sb18rJ1PbdsKsqPA0guxL563vIMwRHgzNrW/uzQuRGN1Cjqd/wonUBAVqHox9KwzHA6vCbM0lXx3k4iQMow==} + borsh@0.7.0: resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} @@ -5589,8 +6428,8 @@ packages: resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} engines: {node: '>=14.16'} - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -5622,8 +6461,8 @@ packages: browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.28.4: + resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5714,11 +6553,8 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-lite@1.0.30001792: - resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} - - caniuse-lite@1.0.30001799: - resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + caniuse-lite@1.0.30001800: + resolution: {integrity: sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==} cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} @@ -5767,6 +6603,9 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -5877,12 +6716,13 @@ packages: resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} engines: {node: '>=20'} + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -5911,17 +6751,17 @@ packages: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} - conventional-changelog-angular@8.3.1: - resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} - engines: {node: '>=18'} + conventional-changelog-angular@9.2.1: + resolution: {integrity: sha512-oWSL6ZhnXbYraOFTK3PgRAQJ8fADDAEv5K6AdeyQPLvjFmhG8+ejL0jZZp/R7vTmGJaBvZEE+sE7dB4bCv7sAw==} + engines: {node: '>=22'} - conventional-changelog-conventionalcommits@9.3.1: - resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==} - engines: {node: '>=18'} + conventional-changelog-conventionalcommits@10.2.0: + resolution: {integrity: sha512-UtlM9GqolY7OmlQh5L/UEVoKsTUpTgUVy1PU8JN5gl5Ydaejb7WRklGliG1SKPxxj7hzA173eG3Kt5fYWE2pmg==} + engines: {node: '>=22'} - conventional-commits-parser@6.4.0: - resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} - engines: {node: '>=18'} + conventional-commits-parser@7.1.0: + resolution: {integrity: sha512-DPp6hkUjvwIivxbkrTiLXeRswNv1A/4GFA2X6scXma0AMa9632V3TwxmrlkUIEtUktiM3Ln+RrSH2xlP3/jUTw==} + engines: {node: '>=22'} hasBin: true convert-source-map@2.0.0: @@ -5991,6 +6831,9 @@ packages: crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + crypto-browserify@3.12.0: resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} @@ -6034,8 +6877,8 @@ packages: typescript: optional: true - d3-array@3.2.1: - resolution: {integrity: sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} d3-color@3.1.0: @@ -6046,8 +6889,8 @@ packages: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} - d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} d3-interpolate@3.0.1: @@ -6082,9 +6925,6 @@ packages: resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} engines: {node: '>=20'} - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -6211,10 +7051,6 @@ packages: dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} - dns-packet@5.6.1: - resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} - engines: {node: '>=6'} - dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -6225,10 +7061,6 @@ packages: resolution: {integrity: sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==} engines: {node: '>=10'} - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - draggabilly@3.0.0: resolution: {integrity: sha512-aEs+B6prbMZQMxc9lgTpCBfyCUhRur/VFucHhIOvlvvdARTj7TcDmX/cdOUtqbjJJUh7+agyJXR5Z6IFe1MxwQ==} @@ -6248,11 +7080,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@4.0.0-beta.68: - resolution: {integrity: sha512-JVmKXJvpyKBzr4xEr9nRlA6wQoInsI2WCfu2F69On49hvyDAmAOMb5ACa51HJksvRdqbSg7Zf0d++NQebY9cFQ==} + effect@4.0.0-beta.97: + resolution: {integrity: sha512-pK03HpQVxGZOWdwDAy/iwvV8u3KYcUf2mOWyWqaut2zau8V2u6ejWP7b4BELjyUIiZWW1fl/s/VJpgZUcTjThg==} - electron-to-chromium@1.5.353: - resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} + electron-to-chromium@1.5.387: + resolution: {integrity: sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==} elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -6319,6 +7151,9 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.0: + resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -6336,11 +7171,8 @@ packages: es-toolkit@1.44.0: resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} - es-toolkit@1.46.1: - resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} - - es-toolkit@1.48.1: - resolution: {integrity: sha512-wfnXlwd5I75eXRtdD2vuEs50xHHESECDsGD7yiQnfFVNoa5522NwXEbmgo98LfiukSQHs+mBM7/YG3qKJB9/mQ==} + es-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} @@ -6351,11 +7183,6 @@ packages: es6-promisify@5.0.0: resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -6439,8 +7266,8 @@ packages: exenv@1.2.2: resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} expect@30.4.1: @@ -6458,8 +7285,8 @@ packages: resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} engines: {node: '> 0.1.90'} - fast-check@4.8.0: - resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} + fast-check@4.9.0: + resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==} engines: {node: '>=12.17.0'} fast-deep-equal@3.1.3: @@ -6487,8 +7314,11 @@ packages: fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} - fast-wrap-ansi@0.2.0: - resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + fast-uri@3.1.3: + resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} fastestsmallesttextencoderdecoder@1.0.22: resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} @@ -6499,6 +7329,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -6560,14 +7393,23 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + fraction.js@4.0.1: resolution: {integrity: sha512-NQYzZw8MUsxSZFQo6E8tKOlmSd/BlDTNOR4puXFSHSwFwNaIlmbortQy5PDN/KnVQ4xWG2NtN0J0hjPw7eE06A==} fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - framer-motion@12.38.0: - resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} + framer-motion@12.42.2: + resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -6643,10 +7485,8 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - git-raw-commits@5.0.1: - resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==} - engines: {node: '>=18'} - hasBin: true + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -6673,8 +7513,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql@16.14.0: - resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==} + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} h3@1.15.11: @@ -6721,6 +7561,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} @@ -6742,8 +7586,8 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hono@4.12.26: - resolution: {integrity: sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==} + hono@4.12.27: + resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} engines: {node: '>=16.9.0'} html-encoding-sniffer@4.0.0: @@ -6767,6 +7611,10 @@ packages: https-browserify@1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -6786,10 +7634,10 @@ packages: i18next-browser-languagedetector@8.2.1: resolution: {integrity: sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==} - i18next@26.1.0: - resolution: {integrity: sha512-dIU6td04DvQuIqVst5S9g0GviTmhZ0DYD4b9ociVGJmuCa5vZ2de/t+Enf4olvj87mF8Y2lwjNQBwC9QZsvzKQ==} + i18next@26.3.6: + resolution: {integrity: sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==} peerDependencies: - typescript: ^5 || ^6 + typescript: ^5 || ^6 || ^7 peerDependenciesMeta: typescript: optional: true @@ -6805,11 +7653,8 @@ packages: idb-keyval@6.2.1: resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} - idb-keyval@6.2.2: - resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} - - idb-keyval@6.2.5: - resolution: {integrity: sha512-eKQkTnS0relYsSOYomx8ozIbmdsQCKUdhyuIaQ2DZgKuaxtyQQMkyD/wlnQN32pO3yutN1b1L8uqwcDKaJd7/Q==} + idb-keyval@6.2.6: + resolution: {integrity: sha512-FY64UEhw+5liMzMQ1R9Mw6AF0+wyBrg1CIA1z4CjI/EvT5ty/SvQcWZgd8s9sgaNhX10Y8UzScTh89tEAls5nA==} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -6824,14 +7669,11 @@ packages: engines: {node: '>=16.x'} hasBin: true - immer@10.2.0: - resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} - - immer@11.1.8: - resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + immer@11.1.11: + resolution: {integrity: sha512-qzXuyXAkPySAGYkfsAwodDPWT8Zm7/Uo5BNt4BjhMhG5WlWyZZ4wQqnWwdS8kjlQ1Cwu6gjw3A6+0gTQwlyYtw==} - immutable@5.1.6: - resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==} + immutable@5.1.9: + resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -6898,6 +7740,9 @@ packages: is-arrayish@0.3.4: resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -6942,10 +7787,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -6965,6 +7806,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-retry-allowed@2.2.0: + resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} + engines: {node: '>=10'} + is-retry-allowed@3.0.0: resolution: {integrity: sha512-9xH0xvoggby+u0uGF7cZXdrutWiBiaFG8ZT4YFPXL8NzkyAwX3AKGLeFQLvzDpM430+nDFBZ1LHkie/8ocL06A==} engines: {node: '>=12'} @@ -7117,6 +7962,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -7126,12 +7974,12 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + js-yaml@3.15.0: + resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true - js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true jsbi@3.2.5: @@ -7214,6 +8062,11 @@ packages: keyvaluestorage-interface@1.0.0: resolution: {integrity: sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==} + knip@6.26.0: + resolution: {integrity: sha512-e9eELEEpBpGTd4H4HB7818/DYj9dMzMyUqAddfYwUN/EbSkgIjOuWEF96W/xHsmV0SDrsdXjIM+oZ2xpPzPsBA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + kubernetes-types@1.30.0: resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} @@ -7226,18 +8079,21 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} - libsodium-sumo@0.7.15: - resolution: {integrity: sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw==} - libsodium-sumo@0.7.16: resolution: {integrity: sha512-x6atrz2AdXCJg6G709x9W9TTJRI6/0NcL5dD0l5GGVqNE48UJmDsjO4RUWYTeyXXUpg+NXZ2SHECaZnFRYzwGA==} - libsodium-wrappers-sumo@0.7.15: - resolution: {integrity: sha512-aSWY8wKDZh5TC7rMvEdTHoyppVq/1dTSAeAR7H6pzd6QRT3vQWcT5pGwCotLcpPEOLXX6VvqihSPkpEhYAjANA==} + libsodium-sumo@0.8.4: + resolution: {integrity: sha512-TMtHShQfVVsaxDygyapvUC3o7YsPgXa/hRWeIgzyFz6w5k/1hirGptCxp1U7XwW3rCskaTTYKgV10v86UiGgNw==} + + libsodium-wrappers-sumo@0.7.10: + resolution: {integrity: sha512-1noz8Mcl/LUzO/iSO/FJzoJyIaPwxl+/+E4CoTIXtsPiEEXQx2sxalmrVWxteLpynqgX0ASo28ChB9NEVRh0Pg==} libsodium-wrappers-sumo@0.7.16: resolution: {integrity: sha512-gR0JEFPeN3831lB9+ogooQk0KH4K5LSMIO5Prd5Q5XYR2wHFtZfPg0eP7t1oJIWq+UIzlU4WVeBxZ97mt28tXw==} + libsodium-wrappers-sumo@0.8.4: + resolution: {integrity: sha512-ql7hcgulKZ3ekfa2DGAogcCKsWU0diA/0nArz1CFzh93WQdb46/Kj18ka/Hifq6uA3Ush34Pc6vU/6HXeRwUkg==} + lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} @@ -7327,8 +8183,8 @@ packages: lit-html@2.8.0: resolution: {integrity: sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==} - lit-html@3.3.2: - resolution: {integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==} + lit-html@3.3.3: + resolution: {integrity: sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==} lit@2.8.0: resolution: {integrity: sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==} @@ -7339,6 +8195,9 @@ packages: lit@3.3.0: resolution: {integrity: sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==} + lit@3.3.3: + resolution: {integrity: sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -7405,10 +8264,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.6: - resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} - engines: {node: 20 || >=22} - lru-cache@11.5.1: resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} engines: {node: 20 || >=22} @@ -7423,6 +8278,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + main-event@1.0.4: + resolution: {integrity: sha512-sKazUjIy2Jalv5lkQ446iOcrx8Q7TkaCuk6xfnzg5uUqMusMLDMPmRDmSNE2kjSVpSTJo4j1bQZusS+Ib7Bvrg==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -7440,6 +8298,9 @@ packages: md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -7449,10 +8310,6 @@ packages: memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - meow@13.2.0: - resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} - engines: {node: '>=18'} - merge-options@3.0.4: resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} engines: {node: '>=10'} @@ -7598,9 +8455,8 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mixpanel-browser@2.78.0: - resolution: {integrity: sha512-K2nsMLnTK0PXcQxhj1aJyGpKyEfo2u7wgZhVm532DTjkoCbJJkuSjDBWJFCH5agEM5oE0aVoCYKd0hZ+i8LsYw==} - engines: {node: '>=20 <26'} + mixpanel-browser@2.81.0: + resolution: {integrity: sha512-FVPaapbpDdZVbU8X6A3N1Ur3PWcr1Kol9sGuHNwExpFYFQVFeeSlaeEyFELvpTp6Q5JgBoLatkDbb/w/81TDJQ==} mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -7616,17 +8472,17 @@ packages: modern-ahocorasick@1.1.0: resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} - motion-dom@12.38.0: - resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} + motion-dom@12.42.2: + resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==} - motion-utils@12.36.0: - resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} motion@10.16.2: resolution: {integrity: sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==} - motion@12.38.0: - resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==} + motion@12.42.2: + resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -7649,15 +8505,15 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msgpackr-extract@3.0.3: - resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + msgpackr-extract@3.0.4: + resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} hasBin: true - msgpackr@2.0.1: - resolution: {integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==} + msgpackr@2.0.4: + resolution: {integrity: sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA==} - msw@2.14.6: - resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==} + msw@2.15.0: + resolution: {integrity: sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -7666,14 +8522,17 @@ packages: typescript: optional: true - multiformats@13.4.1: - resolution: {integrity: sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==} + multiformats@13.4.2: + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} + + multiformats@14.0.4: + resolution: {integrity: sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==} multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} - multipasta@0.2.7: - resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + multipasta@0.2.8: + resolution: {integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==} mute-stream@3.0.0: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} @@ -7682,16 +8541,14 @@ packages: nan@2.23.1: resolution: {integrity: sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==} + nan@2.28.0: + resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==} + nanoassert@2.0.0: resolution: {integrity: sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@3.3.14: - resolution: {integrity: sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==} + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -7711,8 +8568,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - next@16.2.9: - resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==} + next@16.2.10: + resolution: {integrity: sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -7779,8 +8636,9 @@ packages: node-readfiles@0.2.0: resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} - node-releases@2.0.44: - resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} + engines: {node: '>=18'} node-stdlib-browser@1.3.1: resolution: {integrity: sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==} @@ -7798,82 +8656,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - npm@9.9.4: - resolution: {integrity: sha512-NzcQiLpqDuLhavdyJ2J3tGJ/ni/ebcqHVFZkv1C4/6lblraUPbPgCJ4Vhb4oa3FFhRa2Yj9gA58jGH/ztKueNQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - bundledDependencies: - - '@isaacs/string-locale-compare' - - '@npmcli/arborist' - - '@npmcli/config' - - '@npmcli/fs' - - '@npmcli/map-workspaces' - - '@npmcli/package-json' - - '@npmcli/promise-spawn' - - '@npmcli/run-script' - - abbrev - - archy - - cacache - - chalk - - ci-info - - cli-columns - - cli-table3 - - columnify - - fastest-levenshtein - - fs-minipass - - glob - - graceful-fs - - hosted-git-info - - ini - - init-package-json - - is-cidr - - json-parse-even-better-errors - - libnpmaccess - - libnpmdiff - - libnpmexec - - libnpmfund - - libnpmhook - - libnpmorg - - libnpmpack - - libnpmpublish - - libnpmsearch - - libnpmteam - - libnpmversion - - make-fetch-happen - - minimatch - - minipass - - minipass-pipeline - - ms - - node-gyp - - nopt - - normalize-package-data - - npm-audit-report - - npm-install-checks - - npm-package-arg - - npm-pick-manifest - - npm-profile - - npm-registry-fetch - - npm-user-validate - - npmlog - - p-map - - pacote - - parse-conflict-json - - proc-log - - qrcode-terminal - - read - - semver - - sigstore - - spdx-expression-parse - - ssri - - supports-color - - tar - - text-table - - tiny-relative-date - - treeverse - - validate-npm-package-name - - which - - write-file-atomic - nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -7972,16 +8754,8 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - ox@0.14.20: - resolution: {integrity: sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - - ox@0.14.29: - resolution: {integrity: sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==} + ox@0.14.30: + resolution: {integrity: sha512-LI11uu+8iiM1B3CLckgd++YF1a0A2k5wDoM9ZeQMiL21BOzQs6L//BLS6hb1HSEKCyycdDIQLsVQx9MjpcC0hA==} peerDependencies: typescript: '>=5.4.0' peerDependenciesMeta: @@ -8028,6 +8802,13 @@ packages: typescript: optional: true + oxc-parser@0.137.0: + resolution: {integrity: sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.21.3: + resolution: {integrity: sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -8044,8 +8825,8 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-queue@9.0.0: - resolution: {integrity: sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==} + p-queue@9.3.1: + resolution: {integrity: sha512-POWdiIPmsUPGwb4FeQ4OBg46aqmcInSWe45CKDsGHiOBiVQM9chqfQTuqhuTzcg2Vz9faTI65at0KkVyVEiCHw==} engines: {node: '>=20'} p-timeout@7.0.1: @@ -8062,6 +8843,9 @@ packages: pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + pako@2.2.0: + resolution: {integrity: sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -8119,6 +8903,10 @@ packages: resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} engines: {node: '>= 0.10'} + pbkdf2@3.1.6: + resolution: {integrity: sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g==} + engines: {node: '>= 0.10'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -8126,8 +8914,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pify@4.0.1: @@ -8165,13 +8953,13 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - playwright-core@1.61.0: - resolution: {integrity: sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==} + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} engines: {node: '>=18'} hasBin: true - playwright@1.61.0: - resolution: {integrity: sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==} + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} engines: {node: '>=18'} hasBin: true @@ -8226,12 +9014,8 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} preact@10.23.0: @@ -8240,8 +9024,8 @@ packages: preact@10.24.2: resolution: {integrity: sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==} - prettier@3.8.4: - resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + prettier@3.9.4: + resolution: {integrity: sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==} engines: {node: '>=14'} hasBin: true @@ -8266,8 +9050,8 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - progress-events@1.0.1: - resolution: {integrity: sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==} + progress-events@1.1.0: + resolution: {integrity: sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==} promise@8.3.0: resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} @@ -8329,9 +9113,6 @@ packages: pure-rand@8.4.0: resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} - purify-ts@2.1.4: - resolution: {integrity: sha512-PKSMYPJRoxxHdsI9I2/caXmMDoAFd15lHmOAJsq55cl9o2gHH6NWaPTuwRW6cNkTRhZRkmoTN7U6062mRMcIsA==} - pushdata-bitcoin@1.0.1: resolution: {integrity: sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==} @@ -8407,14 +9188,14 @@ packages: peerDependencies: react: ^19.2.7 - react-i18next@17.0.7: - resolution: {integrity: sha512-rwtPXsb/zwzDafN+gytcjF5YnqGQQIRmCQ6DctBC1VSipRB8GD/MWEVrFP42vjMyuYydxWxM8CZRt+yiNuuoHg==} + react-i18next@17.0.9: + resolution: {integrity: sha512-buLzOSqHtXxjf+qgSrLWNTXVZ1jSwO6kUv3uJqSP1roGBPgNnbhFm7OmdVwWcgf2gIbUyP0J333uPyx+Btsi3w==} peerDependencies: - i18next: '>= 26.0.10' + i18next: '>= 26.2.0' react: '>= 16.8.0' react-dom: '*' react-native: '*' - typescript: ^5 || ^6 + typescript: ^5 || ^6 || ^7 peerDependenciesMeta: react-dom: optional: true @@ -8499,8 +9280,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -8566,8 +9347,8 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - recharts@3.8.1: - resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + recharts@3.9.2: + resolution: {integrity: sha512-G4fy+Pk46RaXgwWMh+Nzhyo/lbFAVqXo9gtetlyehe6Ehge9CsgDuOTwQDD+i1+llaLktNBiNq4bhnGlDRXFtw==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -8630,8 +9411,8 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - reselect@5.1.1: - resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -8641,6 +9422,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -8657,8 +9441,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rev-dep@2.15.0: - resolution: {integrity: sha512-7k0dFECQPUQra0zD2ly0DbiKkLA8GyV1cKEOIIjeePc3cRcvQlUg+XSAr+HTzKyBYv89BbGIT+9X7H+IYQKjEg==} + rev-dep@2.18.0: + resolution: {integrity: sha512-4LGLRnHgEizdFq8qZGDUmpLek6XFNfkksEWz3a+xpYwpvAIK1FccepSl4Z7Kz4ZWyS/a2t680KLLp9camSZdLw==} engines: {node: '>=18'} hasBin: true @@ -8692,8 +9476,8 @@ packages: rolldown: optional: true - rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -8826,8 +9610,8 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-cookie-parser@3.1.2: + resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -8856,8 +9640,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.4: - resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} + shell-quote@1.9.0: + resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} engines: {node: '>= 0.4'} should-equal@2.0.0: @@ -8922,6 +9706,10 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + smol-toml@1.7.0: + resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} + engines: {node: '>= 18'} + smoldot@2.0.40: resolution: {integrity: sha512-h6XC/kKDLdZBBTI0X8y4ZxmaZ2KYVVB0+5isCQm6j26ljeNjHZUDOV+hf8VyoE23+jg00wrxNJ2IVcIAURxwtg==} @@ -9074,6 +9862,10 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -9133,8 +9925,8 @@ packages: thread-stream@0.15.2: resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + thread-stream@3.2.0: + resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} @@ -9165,11 +9957,11 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tldts-core@7.0.30: - resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} + tldts-core@7.4.8: + resolution: {integrity: sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==} - tldts@7.0.30: - resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} + tldts@7.4.8: + resolution: {integrity: sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==} hasBin: true tmpl@1.0.5: @@ -9190,8 +9982,8 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - toml@4.1.1: - resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} + toml@4.1.2: + resolution: {integrity: sha512-m0vXfHODcw3gk+KONAOlVQ5yNHc3yS3B1ybM3HS1vqDoS0RWTDDVBVVTYi8hH0k+2OM1vmo9fb1WX9EVqjqfHA==} engines: {node: '>=20'} totalist@3.0.1: @@ -9202,8 +9994,8 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} - tough-cookie@6.0.1: - resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} tr46@0.0.3: @@ -9216,6 +10008,9 @@ packages: tronweb@6.2.2: resolution: {integrity: sha512-jRBf4+7fJ0HUVzveBi0tE21r3EygCNtbYE92T38Sxlwr/x320W2vz+dvGLOIpp4kW/CvJ4HLvtnb6U30A0V2eA==} + tronweb@6.4.0: + resolution: {integrity: sha512-WyrjHKN6YSnV/Dvc4cdULDyXVKJfj4bqQ/8dib6FlOaQ1T/EiOjLtOx24hDgcn8lY/iVMy4Axchzurk1wGcRJg==} + ts-custom-error@3.3.1: resolution: {integrity: sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==} engines: {node: '>=14.0.0'} @@ -9235,16 +10030,16 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + tsx@4.23.0: + resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} engines: {node: '>=18.0.0'} hasBin: true tty-browserify@0.0.1: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} - turbo@2.9.18: - resolution: {integrity: sha512-bwabv6PupzeavybzEoArBAkwq5fnzwf8OFnRtpHwnviFWuwJPFxtyH+aVp36TmIqK3aYYgtTJ3J0m2ysxxSzQg==} + turbo@2.10.4: + resolution: {integrity: sha512-GQpduILaKjoaGljw097ScsSyKTtZSY7cZ3bJktzfTkPMyCf3ShKLuXK2IaOEN2Plziml+ArR7WJ1m+V4VbnaKQ==} hasBin: true tweetnacl-util@0.15.1: @@ -9269,8 +10064,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@5.6.0: - resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} + type-fest@5.8.0: + resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} engines: {node: '>=20'} typed-array-buffer@1.0.3: @@ -9285,6 +10080,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + ua-is-frozen@0.1.2: resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} @@ -9304,15 +10104,21 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - uint8-varint@2.0.4: - resolution: {integrity: sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==} + uint8-varint@2.0.5: + resolution: {integrity: sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==} + + uint8-varint@3.0.0: + resolution: {integrity: sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==} uint8array-tools@0.0.8: resolution: {integrity: sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==} engines: {node: '>=14.0.0'} - uint8arraylist@2.4.8: - resolution: {integrity: sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==} + uint8arraylist@2.4.9: + resolution: {integrity: sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==} + + uint8arraylist@3.0.2: + resolution: {integrity: sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==} uint8arrays@3.1.0: resolution: {integrity: sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==} @@ -9320,8 +10126,15 @@ packages: uint8arrays@3.1.1: resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} - uint8arrays@5.1.0: - resolution: {integrity: sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==} + uint8arrays@5.1.1: + resolution: {integrity: sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==} + + uint8arrays@6.1.1: + resolution: {integrity: sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==} + + unbash@4.0.2: + resolution: {integrity: sha512-8gwNZ29+0/3zmXw7ToIHZtg6wK37xnniRUdBt7B27xZxaxfgR5tGMaGHT0t0dLtBV9fXE7zurh0s6Z1DHVjfWg==} + engines: {node: '>=14'} uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} @@ -9332,17 +10145,14 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - undici-types@7.21.0: - resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==} - - undici-types@7.25.0: - resolution: {integrity: sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA==} + undici-types@7.28.0: + resolution: {integrity: sha512-LJAfY+2w6HGeT8d8J1wNQsUGUEGio6NWWpwdwurQe4f6oojzCFuGLizl1KSve4irsTxyLly1QhEeE6iapdaIvQ==} undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@8.3.0: - resolution: {integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==} + undici@8.7.0: + resolution: {integrity: sha512-N7iQtfyLhIMOFgQubvmLV26svHpO0bqKnAiWotTQCVKCmWrcGbBotPuW1x+xwYZ2VHdSTVUfPQQnlEt1/LouTQ==} engines: {node: '>=22.19.0'} unidragger@3.0.1: @@ -9469,15 +10279,6 @@ packages: '@types/react': optional: true - use-isomorphic-layout-effect@1.2.1: - resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - use-sidecar@1.1.3: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} @@ -9507,6 +10308,9 @@ packages: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} + utf8-codec@1.0.0: + resolution: {integrity: sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -9525,8 +10329,8 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - uuid@14.0.0: - resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} hasBin: true uuid@8.3.2: @@ -9571,8 +10375,8 @@ packages: react: optional: true - valtio@2.1.5: - resolution: {integrity: sha512-vsh1Ixu5mT0pJFZm+Jspvhga5GzHUTYv0/+Th203pLfh3/wbHwxhu/Z2OkZDXIgHfjnjBns7SN9HNcbDvPmaGw==} + valtio@2.1.7: + resolution: {integrity: sha512-DwJhCDpujuQuKdJ2H84VbTjEJJteaSmqsuUltsfbfdbotVfNeTE4K/qc/Wi57I9x8/2ed4JNdjEna7O6PfavRg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -9609,16 +10413,8 @@ packages: typescript: optional: true - viem@2.48.11: - resolution: {integrity: sha512-+WZ5E0dBS6GtKb+1wEk5DeYRRRW42+pFnXCo67Ydodf42sBwO+hu3wnQy66lc4MKmHz+llPVdbyehYr9oTE2iw==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - - viem@2.53.1: - resolution: {integrity: sha512-FhfJ/SW73CVosiyVLmIMVgKDRKYV1AGCLzZoHYvmNayyVff63Qi1ocPCk59LqC/cNw244RbBJjHnmxqXkE7NpA==} + viem@2.55.0: + resolution: {integrity: sha512-5XWSTCTmNvHCeT38Fsp31uofmOZFJdj4nOUH+H2Vh/hzsx9M7r+KgL3fSYUZeVn4H0UyQxjwPFqEaV4M0CI1tA==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -9635,13 +10431,13 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vite@8.0.16: - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + vite@8.1.4: + resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.3.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -9692,20 +10488,20 @@ packages: '@types/react-dom': optional: true - vitest@4.1.9: - resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.9 - '@vitest/browser-preview': 4.1.9 - '@vitest/browser-webdriverio': 4.1.9 - '@vitest/coverage-istanbul': 4.1.9 - '@vitest/coverage-v8': 4.1.9 - '@vitest/ui': 4.1.9 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -9747,17 +10543,21 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - wagmi@3.6.14: - resolution: {integrity: sha512-kHRaaD9DeFaUN6WcVMEKApTwVwImt0fGINh04Z4WYXLJhkb0rBxG4js+3t8EzXsV/veec5dV90j7jfGwjGSsqQ==} + wagmi@3.7.1: + resolution: {integrity: sha512-CTocC9w6TW3X14d1NB2dDRpDz4qSM4IDttIGVaSAArjoHlptfJZIkBNXLktUNXk52dUfitLucvTwCWngy8NBAQ==} peerDependencies: '@tanstack/react-query': '>=5.0.0' react: '>=18' - typescript: '>=5.7.3' + typescript: '>=5.9.3' viem: 2.x peerDependenciesMeta: typescript: optional: true + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -9874,18 +10674,6 @@ packages: utf-8-validate: optional: true - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@7.5.11: resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} engines: {node: '>=8.3.0'} @@ -9946,30 +10734,6 @@ packages: utf-8-validate: optional: true - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -9997,9 +10761,6 @@ packages: resolution: {integrity: sha512-vi2OjuNkiaP8nv1j+nqHp8GZwwEjO6Y8+j/OuVMg6M4LwXEwyHdIj33dlg7cyY1Lw5+jb9HqFOQvABhaywVbTQ==} engines: {node: '>=18.0.0'} - xstate@5.31.1: - resolution: {integrity: sha512-3P7t7GQ61BvLu+8Cj6Zq7rcS34vecL9pvfN2ucUWmIFIUG+rAREviOs4Xy4OO3BuJHSz6RLU8eqDXxSbVotjDQ==} - xstream@11.14.0: resolution: {integrity: sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==} @@ -10123,10 +10884,9 @@ packages: snapshots: - '@acemir/cssom@0.9.31': - optional: true + '@acemir/cssom@0.9.31': {} - '@adobe/css-tools@4.4.4': {} + '@adobe/css-tools@4.5.0': {} '@adraffy/ens-normalize@1.10.1': {} @@ -10135,11 +10895,10 @@ snapshots: '@asamuzakjp/css-color@4.1.2': dependencies: '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.8(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 lru-cache: 11.5.1 - optional: true '@asamuzakjp/dom-selector@6.8.1': dependencies: @@ -10148,16 +10907,41 @@ snapshots: css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 lru-cache: 11.5.1 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@ast-grep/cli-darwin-arm64@0.44.1': optional: true - '@asamuzakjp/nwsapi@2.3.9': + '@ast-grep/cli-darwin-x64@0.44.1': optional: true - '@babel/code-frame@7.29.0': + '@ast-grep/cli-linux-arm64-gnu@0.44.1': + optional: true + + '@ast-grep/cli-linux-x64-gnu@0.44.1': + optional: true + + '@ast-grep/cli-win32-arm64-msvc@0.44.1': + optional: true + + '@ast-grep/cli-win32-ia32-msvc@0.44.1': + optional: true + + '@ast-grep/cli-win32-x64-msvc@0.44.1': + optional: true + + '@ast-grep/cli@0.44.1': dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 + detect-libc: 2.1.2 + optionalDependencies: + '@ast-grep/cli-darwin-arm64': 0.44.1 + '@ast-grep/cli-darwin-x64': 0.44.1 + '@ast-grep/cli-linux-arm64-gnu': 0.44.1 + '@ast-grep/cli-linux-x64-gnu': 0.44.1 + '@ast-grep/cli-win32-arm64-msvc': 0.44.1 + '@ast-grep/cli-win32-ia32-msvc': 0.44.1 + '@ast-grep/cli-win32-x64-msvc': 0.44.1 '@babel/code-frame@7.29.7': dependencies: @@ -10165,19 +10949,19 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.3': {} + '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.0': + '@babel/core@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -10187,14 +10971,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.7': dependencies: '@babel/parser': 7.29.7 @@ -10203,60 +10979,48 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.28.6': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/compat-data': 7.29.3 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.2 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.4 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} - '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-string-parser@7.29.7': {} '@babel/helper-string-parser@8.0.0': {} - '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-identifier@7.29.7': {} '@babel/helper-validator-identifier@8.0.2': {} - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/helper-validator-option@7.29.7': {} - '@babel/parser@7.29.3': + '@babel/helpers@7.29.7': dependencies: - '@babel/types': 7.29.0 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 '@babel/parser@7.29.7': dependencies: @@ -10266,85 +11030,85 @@ snapshots: dependencies: '@babel/types': 8.0.0 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/runtime@7.26.10': dependencies: @@ -10352,11 +11116,7 @@ snapshots: '@babel/runtime@7.29.2': {} - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 + '@babel/runtime@7.29.7': {} '@babel/template@7.29.7': dependencies: @@ -10364,18 +11124,6 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -10388,11 +11136,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -10403,47 +11146,71 @@ snapshots: '@babel/helper-string-parser': 8.0.0 '@babel/helper-validator-identifier': 8.0.2 + '@base-org/account@2.4.0(@types/react@19.2.17)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@coinbase/cdp-sdk': 1.52.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@noble/hashes': 1.4.0 + clsx: 1.2.1 + eventemitter3: 5.0.1 + idb-keyval: 6.2.1 + ox: 0.6.9(typescript@7.0.2)(zod@3.25.76) + preact: 10.24.2 + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + zustand: 5.0.3(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) + transitivePeerDependencies: + - '@types/react' + - bufferutil + - debug + - fastestsmallesttextencoderdecoder + - immer + - react + - typescript + - use-sync-external-store + - utf-8-validate + - zod + optional: true + '@biglup/is-cid@1.0.3': dependencies: '@multiformats/mafmt': 12.1.6 '@multiformats/multiaddr': 12.5.1 iso-url: 1.2.1 - multiformats: 13.4.1 - uint8arrays: 5.1.0 + multiformats: 13.4.2 + uint8arrays: 5.1.1 - '@biomejs/biome@2.5.0': + '@biomejs/biome@2.5.3': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.5.0 - '@biomejs/cli-darwin-x64': 2.5.0 - '@biomejs/cli-linux-arm64': 2.5.0 - '@biomejs/cli-linux-arm64-musl': 2.5.0 - '@biomejs/cli-linux-x64': 2.5.0 - '@biomejs/cli-linux-x64-musl': 2.5.0 - '@biomejs/cli-win32-arm64': 2.5.0 - '@biomejs/cli-win32-x64': 2.5.0 + '@biomejs/cli-darwin-arm64': 2.5.3 + '@biomejs/cli-darwin-x64': 2.5.3 + '@biomejs/cli-linux-arm64': 2.5.3 + '@biomejs/cli-linux-arm64-musl': 2.5.3 + '@biomejs/cli-linux-x64': 2.5.3 + '@biomejs/cli-linux-x64-musl': 2.5.3 + '@biomejs/cli-win32-arm64': 2.5.3 + '@biomejs/cli-win32-x64': 2.5.3 - '@biomejs/cli-darwin-arm64@2.5.0': + '@biomejs/cli-darwin-arm64@2.5.3': optional: true - '@biomejs/cli-darwin-x64@2.5.0': + '@biomejs/cli-darwin-x64@2.5.3': optional: true - '@biomejs/cli-linux-arm64-musl@2.5.0': + '@biomejs/cli-linux-arm64-musl@2.5.3': optional: true - '@biomejs/cli-linux-arm64@2.5.0': + '@biomejs/cli-linux-arm64@2.5.3': optional: true - '@biomejs/cli-linux-x64-musl@2.5.0': + '@biomejs/cli-linux-x64-musl@2.5.3': optional: true - '@biomejs/cli-linux-x64@2.5.0': + '@biomejs/cli-linux-x64@2.5.3': optional: true - '@biomejs/cli-win32-arm64@2.5.0': + '@biomejs/cli-win32-arm64@2.5.3': optional: true - '@biomejs/cli-win32-x64@2.5.0': + '@biomejs/cli-win32-x64@2.5.3': optional: true '@blazediff/core@1.9.1': {} @@ -10451,15 +11218,15 @@ snapshots: '@cardano-ogmios/client@6.9.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)': dependencies: '@cardano-ogmios/schema': 6.9.0 - '@cardanosolutions/json-bigint': 1.0.2 + '@cardanosolutions/json-bigint': 1.1.0 '@types/json-bigint': 1.0.4 bech32: 2.0.0 cross-fetch: 3.2.0(encoding@0.1.13) fastq: 1.20.1 - isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - nanoid: 3.3.14 + isomorphic-ws: 4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + nanoid: 3.3.15 ts-custom-error: 3.3.1 - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding @@ -10467,39 +11234,13 @@ snapshots: '@cardano-ogmios/schema@6.9.0': {} - '@cardano-sdk/core@0.43.0(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': - dependencies: - '@biglup/is-cid': 1.0.3 - '@cardano-ogmios/client': 6.9.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - '@cardano-ogmios/schema': 6.9.0 - '@cardano-sdk/crypto': 0.1.32 - '@cardano-sdk/util': 0.15.7 - '@foxglove/crc': 0.0.3 - '@scure/base': 1.2.6 - fraction.js: 4.0.1 - ip-address: 9.0.5 - lodash: 4.18.1 - ts-custom-error: 3.3.1 - ts-log: 2.2.7 - web-encoding: 1.1.5 - optionalDependencies: - rxjs: 7.8.2 - transitivePeerDependencies: - - '@dcspark/cardano-multiplatform-lib-asmjs' - - '@dcspark/cardano-multiplatform-lib-browser' - - '@dcspark/cardano-multiplatform-lib-nodejs' - - bufferutil - - encoding - - react-native-b4a - - utf-8-validate - - '@cardano-sdk/core@0.45.10(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@cardano-sdk/core@0.46.12(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': dependencies: '@biglup/is-cid': 1.0.3 '@cardano-ogmios/client': 6.9.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@cardano-ogmios/schema': 6.9.0 - '@cardano-sdk/crypto': 0.2.3 - '@cardano-sdk/util': 0.16.0 + '@cardano-sdk/crypto': 0.4.7 + '@cardano-sdk/util': 0.17.1 '@foxglove/crc': 0.0.3 '@scure/base': 1.2.6 fraction.js: 4.0.1 @@ -10519,78 +11260,56 @@ snapshots: - react-native-b4a - utf-8-validate - '@cardano-sdk/core@0.46.11(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@cardano-sdk/core@0.46.15': dependencies: '@biglup/is-cid': 1.0.3 - '@cardano-ogmios/client': 6.9.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - '@cardano-ogmios/schema': 6.9.0 - '@cardano-sdk/crypto': 0.4.4 - '@cardano-sdk/util': 0.17.1 + '@cardano-sdk/crypto': 0.4.7 + '@cardano-sdk/util': 0.17.2 '@foxglove/crc': 0.0.3 '@scure/base': 1.2.6 fraction.js: 4.0.1 - ip-address: 9.0.5 + ip-address: 10.2.0 lodash: 4.18.1 ts-custom-error: 3.3.1 ts-log: 2.2.7 web-encoding: 1.1.5 - optionalDependencies: - rxjs: 7.8.2 transitivePeerDependencies: - '@dcspark/cardano-multiplatform-lib-asmjs' - - '@dcspark/cardano-multiplatform-lib-browser' - - '@dcspark/cardano-multiplatform-lib-nodejs' - - bufferutil - - encoding - - react-native-b4a - - utf-8-validate - - '@cardano-sdk/crypto@0.1.32': - dependencies: - '@cardano-sdk/util': 0.15.7 - blake2b: 2.1.4 - i: 0.3.7 - libsodium-wrappers-sumo: 0.7.15 - lodash: 4.18.1 - npm: 9.9.4 - pbkdf2: 3.1.5 - ts-custom-error: 3.3.1 - ts-log: 2.2.7 - transitivePeerDependencies: + - '@dcspark/cardano-multiplatform-lib-browser' + - '@dcspark/cardano-multiplatform-lib-nodejs' - react-native-b4a - '@cardano-sdk/crypto@0.2.3': + '@cardano-sdk/crypto@0.4.5': dependencies: - '@cardano-sdk/util': 0.16.0 + '@cardano-sdk/util': 0.17.1 blake2b: 2.1.4 i: 0.3.7 - libsodium-wrappers-sumo: 0.7.15 + libsodium-wrappers-sumo: 0.7.10 lodash: 4.18.1 - npm: 9.9.4 - pbkdf2: 3.1.5 + pbkdf2: 3.1.6 ts-custom-error: 3.3.1 ts-log: 2.2.7 transitivePeerDependencies: - react-native-b4a - '@cardano-sdk/crypto@0.4.4': + '@cardano-sdk/crypto@0.4.7': dependencies: - '@cardano-sdk/util': 0.17.1 + '@cardano-sdk/util': 0.17.2 blake2b: 2.1.4 i: 0.3.7 - libsodium-wrappers-sumo: 0.7.15 + libsodium-wrappers-sumo: 0.8.4 lodash: 4.18.1 - pbkdf2: 3.1.5 + pbkdf2: 3.1.6 ts-custom-error: 3.3.1 ts-log: 2.2.7 transitivePeerDependencies: - react-native-b4a - '@cardano-sdk/dapp-connector@0.13.25(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@cardano-sdk/dapp-connector@0.13.29': dependencies: - '@cardano-sdk/core': 0.46.11(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/crypto': 0.4.4 - '@cardano-sdk/util': 0.17.1 + '@cardano-sdk/core': 0.46.15 + '@cardano-sdk/crypto': 0.4.7 + '@cardano-sdk/util': 0.17.2 ts-custom-error: 3.3.1 ts-log: 2.2.7 webextension-polyfill: 0.8.0 @@ -10598,17 +11317,13 @@ snapshots: - '@dcspark/cardano-multiplatform-lib-asmjs' - '@dcspark/cardano-multiplatform-lib-browser' - '@dcspark/cardano-multiplatform-lib-nodejs' - - bufferutil - - encoding - react-native-b4a - - rxjs - - utf-8-validate - '@cardano-sdk/input-selection@0.13.34(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@cardano-sdk/input-selection@0.14.28(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': dependencies: - '@cardano-sdk/core': 0.43.0(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/key-management': 0.25.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - '@cardano-sdk/util': 0.15.7 + '@cardano-sdk/core': 0.46.12(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@cardano-sdk/key-management': 0.29.17 + '@cardano-sdk/util': 0.17.1 bignumber.js: 9.3.1 lodash: 4.18.1 ts-custom-error: 3.3.1 @@ -10622,18 +11337,18 @@ snapshots: - rxjs - utf-8-validate - '@cardano-sdk/key-management@0.25.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)': + '@cardano-sdk/key-management@0.29.17': dependencies: - '@cardano-sdk/core': 0.43.0(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/crypto': 0.1.32 - '@cardano-sdk/dapp-connector': 0.13.25(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/util': 0.15.7 + '@cardano-sdk/core': 0.46.15 + '@cardano-sdk/crypto': 0.4.7 + '@cardano-sdk/dapp-connector': 0.13.29 + '@cardano-sdk/util': 0.17.2 '@emurgo/cardano-message-signing-nodejs': 1.1.0 bip39: 3.1.0 chacha: 2.1.0 get-random-values: 2.1.0 lodash: 4.18.1 - pbkdf2: 3.1.5 + pbkdf2: 3.1.6 rxjs: 7.8.2 ts-custom-error: 3.3.1 ts-log: 2.2.7 @@ -10641,30 +11356,17 @@ snapshots: - '@dcspark/cardano-multiplatform-lib-asmjs' - '@dcspark/cardano-multiplatform-lib-browser' - '@dcspark/cardano-multiplatform-lib-nodejs' - - bufferutil - - encoding - react-native-b4a - - utf-8-validate - '@cardano-sdk/util@0.15.7': - dependencies: - bech32: 2.0.0 - lodash: 4.18.1 - serialize-error: 8.1.0 - ts-custom-error: 3.3.1 - ts-log: 2.2.7 - type-fest: 2.19.0 - - '@cardano-sdk/util@0.16.0': + '@cardano-sdk/util@0.17.1': dependencies: bech32: 2.0.0 lodash: 4.18.1 serialize-error: 8.1.0 ts-custom-error: 3.3.1 ts-log: 2.2.7 - type-fest: 2.19.0 - '@cardano-sdk/util@0.17.1': + '@cardano-sdk/util@0.17.2': dependencies: bech32: 2.0.0 lodash: 4.18.1 @@ -10672,9 +11374,7 @@ snapshots: ts-custom-error: 3.3.1 ts-log: 2.2.7 - '@cardanosolutions/json-bigint@1.0.2': - dependencies: - bignumber.js: 9.3.1 + '@cardanosolutions/json-bigint@1.1.0': {} '@chain-registry/client@1.53.355(encoding@0.1.13)': dependencies: @@ -10708,16 +11408,38 @@ snapshots: dependencies: '@chainsafe/is-ip': 2.1.0 - '@coinbase/wallet-sdk@4.3.6(@types/react@19.2.17)(bufferutil@4.0.9)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': + '@coinbase/cdp-sdk@1.52.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana-program/system': 0.10.0(@solana/kit@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana-program/token': 0.9.0(@solana/kit@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/kit': 5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + abitype: 1.0.6(typescript@7.0.2)(zod@3.25.76) + axios: 1.16.0 + axios-retry: 4.5.0(axios@1.16.0) + bs58: 6.0.0 + jose: 6.2.3 + md5: 2.3.0 + uncrypto: 0.1.3 + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + zod: 3.25.76 + transitivePeerDependencies: + - bufferutil + - debug + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + optional: true + + '@coinbase/wallet-sdk@4.3.6(@types/react@19.2.17)(bufferutil@4.0.9)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@noble/hashes': 1.4.0 clsx: 1.2.1 eventemitter3: 5.0.1 idb-keyval: 6.2.1 - ox: 0.6.9(typescript@6.0.3)(zod@3.25.76) + ox: 0.6.9(typescript@7.0.2)(zod@3.25.76) preact: 10.24.2 - viem: 2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - zustand: 5.0.3(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + zustand: 5.0.3(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) transitivePeerDependencies: - '@types/react' - bufferutil @@ -10729,13 +11451,14 @@ snapshots: - zod optional: true - '@commitlint/cli@21.0.2(@types/node@25.7.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)': + '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2)': dependencies: - '@commitlint/format': 21.0.1 - '@commitlint/lint': 21.0.2 - '@commitlint/load': 21.0.2(@types/node@25.7.0)(typescript@6.0.3) - '@commitlint/read': 21.0.2(conventional-commits-parser@6.4.0) - '@commitlint/types': 21.0.1 + '@commitlint/config-conventional': 21.2.0 + '@commitlint/format': 21.2.0 + '@commitlint/lint': 21.2.0 + '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@7.0.2) + '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.0) + '@commitlint/types': 21.2.0 tinyexec: 1.2.4 yargs: 18.0.0 transitivePeerDependencies: @@ -10744,106 +11467,110 @@ snapshots: - conventional-commits-parser - typescript - '@commitlint/config-conventional@21.0.2': + '@commitlint/config-conventional@21.2.0': dependencies: - '@commitlint/types': 21.0.1 - conventional-changelog-conventionalcommits: 9.3.1 + '@commitlint/types': 21.2.0 + conventional-changelog-conventionalcommits: 10.2.0 - '@commitlint/config-validator@21.0.1': + '@commitlint/config-validator@21.2.0': dependencies: - '@commitlint/types': 21.0.1 + '@commitlint/types': 21.2.0 ajv: 8.20.0 - '@commitlint/ensure@21.0.1': + '@commitlint/ensure@21.2.0': dependencies: - '@commitlint/types': 21.0.1 - es-toolkit: 1.48.1 + '@commitlint/types': 21.2.0 + es-toolkit: 1.49.0 '@commitlint/execute-rule@21.0.1': {} - '@commitlint/format@21.0.1': + '@commitlint/format@21.2.0': dependencies: - '@commitlint/types': 21.0.1 + '@commitlint/types': 21.2.0 picocolors: 1.1.1 - '@commitlint/is-ignored@21.0.2': + '@commitlint/is-ignored@21.2.0': dependencies: - '@commitlint/types': 21.0.1 + '@commitlint/types': 21.2.0 semver: 7.8.5 - '@commitlint/lint@21.0.2': + '@commitlint/lint@21.2.0': dependencies: - '@commitlint/is-ignored': 21.0.2 - '@commitlint/parse': 21.0.2 - '@commitlint/rules': 21.0.2 - '@commitlint/types': 21.0.1 + '@commitlint/is-ignored': 21.2.0 + '@commitlint/parse': 21.2.0 + '@commitlint/rules': 21.2.0 + '@commitlint/types': 21.2.0 - '@commitlint/load@21.0.2(@types/node@25.7.0)(typescript@6.0.3)': + '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@7.0.2)': dependencies: - '@commitlint/config-validator': 21.0.1 + '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 - '@commitlint/resolve-extends': 21.0.1 - '@commitlint/types': 21.0.1 - cosmiconfig: 9.0.2(typescript@6.0.3) - cosmiconfig-typescript-loader: 6.3.0(@types/node@25.7.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) - es-toolkit: 1.48.1 + '@commitlint/resolve-extends': 21.2.0 + '@commitlint/types': 21.2.0 + cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) + es-toolkit: 1.49.0 is-plain-obj: 4.1.0 picocolors: 1.1.1 transitivePeerDependencies: - '@types/node' - typescript - '@commitlint/message@21.0.2': {} + '@commitlint/message@21.2.0': {} - '@commitlint/parse@21.0.2': + '@commitlint/parse@21.2.0': dependencies: - '@commitlint/types': 21.0.1 - conventional-changelog-angular: 8.3.1 - conventional-commits-parser: 6.4.0 + '@commitlint/types': 21.2.0 + conventional-changelog-angular: 9.2.1 + conventional-commits-parser: 7.1.0 - '@commitlint/read@21.0.2(conventional-commits-parser@6.4.0)': + '@commitlint/read@21.2.1(conventional-commits-parser@7.1.0)': dependencies: - '@commitlint/top-level': 21.0.2 - '@commitlint/types': 21.0.1 - git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0) + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.0 + '@conventional-changelog/git-client': 3.1.0(conventional-commits-parser@7.1.0) tinyexec: 1.2.4 transitivePeerDependencies: - conventional-commits-filter - conventional-commits-parser - '@commitlint/resolve-extends@21.0.1': + '@commitlint/resolve-extends@21.2.0': dependencies: - '@commitlint/config-validator': 21.0.1 - '@commitlint/types': 21.0.1 - es-toolkit: 1.48.1 + '@commitlint/config-validator': 21.2.0 + '@commitlint/types': 21.2.0 + es-toolkit: 1.49.0 global-directory: 5.0.0 resolve-from: 5.0.0 - '@commitlint/rules@21.0.2': + '@commitlint/rules@21.2.0': dependencies: - '@commitlint/ensure': 21.0.1 - '@commitlint/message': 21.0.2 + '@commitlint/ensure': 21.2.0 + '@commitlint/message': 21.2.0 '@commitlint/to-lines': 21.0.1 - '@commitlint/types': 21.0.1 + '@commitlint/types': 21.2.0 '@commitlint/to-lines@21.0.1': {} - '@commitlint/top-level@21.0.2': + '@commitlint/top-level@21.2.0': dependencies: escalade: 3.2.0 - '@commitlint/types@21.0.1': + '@commitlint/types@21.2.0': dependencies: - conventional-commits-parser: 6.4.0 + conventional-commits-parser: 7.1.0 picocolors: 1.1.1 - '@conventional-changelog/git-client@2.7.0(conventional-commits-parser@6.4.0)': + '@conventional-changelog/git-client@3.1.0(conventional-commits-parser@7.1.0)': dependencies: - '@simple-libs/child-process-utils': 1.0.2 - '@simple-libs/stream-utils': 1.2.0 + '@simple-libs/child-process-utils': 2.0.0 + '@simple-libs/stream-utils': 2.0.0 semver: 7.8.5 optionalDependencies: - conventional-commits-parser: 6.4.0 + conventional-commits-parser: 7.1.0 + + '@conventional-changelog/template@1.2.0': {} + + '@conventional-changelog/template@1.2.1': {} '@cosmjs/amino@0.32.4': dependencies: @@ -10881,7 +11608,7 @@ snapshots: '@cosmjs/math': 0.32.4 '@cosmjs/utils': 0.32.4 '@noble/hashes': 1.8.0 - bn.js: 5.2.3 + bn.js: 5.2.4 elliptic: 6.6.1 libsodium-wrappers-sumo: 0.7.16 @@ -10914,7 +11641,7 @@ snapshots: '@cosmjs/math@0.32.4': dependencies: - bn.js: 5.2.3 + bn.js: 5.2.4 '@cosmjs/math@0.36.2': {} @@ -10939,8 +11666,8 @@ snapshots: '@cosmjs/socket@0.36.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@cosmjs/stream': 0.36.2 - isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + isomorphic-ws: 4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) xstream: 11.14.0 transitivePeerDependencies: - bufferutil @@ -10983,7 +11710,7 @@ snapshots: '@cosmjs/utils@0.36.2': {} - '@cosmos-kit/core@2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10)': + '@cosmos-kit/core@2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10)': dependencies: '@chain-registry/client': 1.53.355(encoding@0.1.13) '@chain-registry/keplr': 1.74.577 @@ -10993,7 +11720,7 @@ snapshots: '@cosmjs/proto-signing': 0.36.2 '@cosmjs/stargate': 0.36.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@dao-dao/cosmiframe': 1.0.0(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2) - '@walletconnect/types': 2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) bowser: 2.11.0 cosmjs-types: 0.10.1 events: 3.3.0 @@ -11024,12 +11751,12 @@ snapshots: - uploadthing - utf-8-validate - '@cosmos-kit/keplr-extension@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(utf-8-validate@5.0.10)': + '@cosmos-kit/keplr-extension@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(utf-8-validate@5.0.10)': dependencies: '@chain-registry/keplr': 1.74.577 '@cosmjs/amino': 0.36.2 '@cosmjs/proto-signing': 0.36.2 - '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) '@keplr-wallet/provider-extension': 0.12.313(starknet@6.23.1(encoding@0.1.13)) '@keplr-wallet/types': 0.12.313(starknet@6.23.1(encoding@0.1.13)) transitivePeerDependencies: @@ -11058,16 +11785,16 @@ snapshots: - uploadthing - utf-8-validate - '@cosmos-kit/keplr-mobile@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@cosmos-kit/keplr-mobile@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@chain-registry/keplr': 1.74.577 '@cosmjs/amino': 0.36.2 '@cosmjs/proto-signing': 0.36.2 - '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) - '@cosmos-kit/keplr-extension': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(utf-8-validate@5.0.10) - '@cosmos-kit/walletconnect': 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/keplr-extension': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(utf-8-validate@5.0.10) + '@cosmos-kit/walletconnect': 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@keplr-wallet/provider-extension': 0.12.313(starknet@6.23.1(encoding@0.1.13)) - '@keplr-wallet/wc-client': 0.12.313(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(starknet@6.23.1(encoding@0.1.13)) + '@keplr-wallet/wc-client': 0.12.313(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(starknet@6.23.1(encoding@0.1.13)) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -11098,10 +11825,10 @@ snapshots: - utf-8-validate - zod - '@cosmos-kit/keplr@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@cosmos-kit/keplr@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@cosmos-kit/keplr-extension': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(utf-8-validate@5.0.10) - '@cosmos-kit/keplr-mobile': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@cosmos-kit/keplr-extension': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(utf-8-validate@5.0.10) + '@cosmos-kit/keplr-mobile': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(starknet@6.23.1(encoding@0.1.13))(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -11134,12 +11861,12 @@ snapshots: - utf-8-validate - zod - '@cosmos-kit/leap-extension@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10)': + '@cosmos-kit/leap-extension@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10)': dependencies: '@chain-registry/keplr': 1.74.577 '@cosmjs/amino': 0.36.2 '@cosmjs/proto-signing': 0.36.2 - '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -11165,12 +11892,12 @@ snapshots: - uploadthing - utf-8-validate - '@cosmos-kit/leap-metamask-cosmos-snap@0.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10)': + '@cosmos-kit/leap-metamask-cosmos-snap@0.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10)': dependencies: '@chain-registry/keplr': 1.74.577 '@cosmjs/amino': 0.36.2 '@cosmjs/proto-signing': 0.36.2 - '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) '@leapwallet/cosmos-snap-provider': 0.1.26 '@metamask/providers': 11.1.2 cosmjs-types: 0.10.1 @@ -11199,11 +11926,11 @@ snapshots: - uploadthing - utf-8-validate - '@cosmos-kit/leap-mobile@2.16.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@cosmos-kit/leap-mobile@2.16.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@chain-registry/keplr': 1.74.577 - '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) - '@cosmos-kit/walletconnect': 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/walletconnect': 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -11234,11 +11961,11 @@ snapshots: - utf-8-validate - zod - '@cosmos-kit/leap@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@cosmos-kit/leap@2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@cosmos-kit/leap-extension': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) - '@cosmos-kit/leap-metamask-cosmos-snap': 0.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) - '@cosmos-kit/leap-mobile': 2.16.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@cosmos-kit/leap-extension': 2.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/leap-metamask-cosmos-snap': 0.17.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(cosmjs-types@0.10.1)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@cosmos-kit/leap-mobile': 2.16.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -11270,14 +11997,14 @@ snapshots: - utf-8-validate - zod - '@cosmos-kit/walletconnect@2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@cosmos-kit/walletconnect@2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@cosmjs/amino': 0.36.2 '@cosmjs/proto-signing': 0.36.2 - '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) - '@walletconnect/sign-client': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@6.0.3)(zod@3.25.76) + '@cosmos-kit/core': 2.18.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(utf-8-validate@5.0.10) + '@walletconnect/sign-client': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -11306,35 +12033,29 @@ snapshots: - utf-8-validate - zod - '@csstools/color-helpers@6.0.2': - optional: true + '@csstools/color-helpers@6.1.0': {} '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - optional: true - '@csstools/css-color-parser@4.1.8(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 6.0.2 + '@csstools/color-helpers': 6.1.0 '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - optional: true '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-tokenizer': 4.0.0 - optional: true - '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 - optional: true - '@csstools/css-tokenizer@4.0.0': - optional: true + '@csstools/css-tokenizer@4.0.0': {} '@dao-dao/cosmiframe@1.0.0(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)': dependencies: @@ -11388,7 +12109,7 @@ snapshots: '@dedot/types': 0.13.2 '@dedot/utils': 0.13.2 handlebars: 4.7.9 - prettier: 3.8.4 + prettier: 3.9.4 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -11450,38 +12171,63 @@ snapshots: '@scure/base': 1.2.6 eventemitter3: 5.0.4 - '@effect/openapi-generator@4.0.0-beta.68(@effect/platform-node@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.68)': + '@dnsquery/dns-packet@6.1.1': + dependencies: + '@leichtgewicht/ip-codec': 2.0.5 + utf8-codec: 1.0.0 + + '@effect/atom-react@4.0.0-beta.97(effect@4.0.0-beta.97)(react@19.2.7)(scheduler@0.27.0)': + dependencies: + effect: 4.0.0-beta.97 + react: 19.2.7 + scheduler: 0.27.0 + + '@effect/openapi-generator@4.0.0-beta.97(@effect/platform-node@4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.97)(encoding@0.1.13)': + dependencies: + '@effect/platform-node': 4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(ioredis@5.10.1)(utf-8-validate@5.0.10) + effect: 4.0.0-beta.97 + swagger2openapi: 7.0.8(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + '@effect/platform-browser@4.0.0-beta.97(effect@4.0.0-beta.97)': dependencies: - '@effect/platform-node': 4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10) - effect: 4.0.0-beta.68 + effect: 4.0.0-beta.97 + multipasta: 0.2.8 - '@effect/platform-node-shared@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(utf-8-validate@5.0.10)': + '@effect/platform-node-shared@4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(utf-8-validate@5.0.10)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.68 - ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + effect: 4.0.0-beta.97 + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10)': + '@effect/platform-node@4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(ioredis@5.10.1)(utf-8-validate@5.0.10)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(utf-8-validate@5.0.10) - effect: 4.0.0-beta.68 + '@effect/platform-node-shared': 4.0.0-beta.97(bufferutil@4.0.9)(effect@4.0.0-beta.97)(utf-8-validate@5.0.10) + effect: 4.0.0-beta.97 ioredis: 5.10.1 mime: 4.1.0 - undici: 8.3.0 + undici: 8.7.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@emnapi/core@1.10.0': + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.11.1': dependencies: - '@emnapi/wasi-threads': 1.2.1 + '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.10.0': + '@emnapi/runtime@1.11.0': dependencies: tslib: 2.8.1 optional: true @@ -11491,7 +12237,12 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 optional: true @@ -11504,159 +12255,81 @@ snapshots: '@emurgo/cardano-serialization-lib-nodejs@13.2.0': {} - '@esbuild/aix-ppc64@0.28.0': - optional: true - '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/android-arm64@0.28.0': - optional: true - '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.28.0': - optional: true - '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-x64@0.28.0': - optional: true - '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.28.0': - optional: true - '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/darwin-x64@0.28.0': - optional: true - '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.28.0': - optional: true - '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.28.0': - optional: true - '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.28.0': - optional: true - '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.28.0': - optional: true - '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/linux-ia32@0.28.0': - optional: true - '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/linux-loong64@0.28.0': - optional: true - '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.28.0': - optional: true - '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/linux-ppc64@0.28.0': - optional: true - '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.28.0': - optional: true - '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/linux-s390x@0.28.0': - optional: true - '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-x64@0.28.0': - optional: true - '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.28.0': - optional: true - '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/netbsd-x64@0.28.0': - optional: true - '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.28.0': - optional: true - '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.28.0': - optional: true - '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.28.0': - optional: true - '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.28.0': - optional: true - '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/win32-arm64@0.28.0': - optional: true - '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/win32-ia32@0.28.0': - optional: true - '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/win32-x64@0.28.0': - optional: true - '@esbuild/win32-x64@0.28.1': optional: true @@ -11722,29 +12395,29 @@ snapshots: '@exodus/schemasafe@1.3.0': {} - '@faker-js/faker@10.4.0': {} + '@faker-js/faker@10.5.0': {} '@fivebinaries/coin-selection@3.0.0': dependencies: '@emurgo/cardano-serialization-lib-browser': 13.2.1 '@emurgo/cardano-serialization-lib-nodejs': 13.2.0 - '@floating-ui/core@1.7.3': + '@floating-ui/core@1.7.5': dependencies: - '@floating-ui/utils': 0.2.10 + '@floating-ui/utils': 0.2.11 - '@floating-ui/dom@1.7.4': + '@floating-ui/dom@1.7.6': dependencies: - '@floating-ui/core': 1.7.3 - '@floating-ui/utils': 0.2.10 + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 - '@floating-ui/react-dom@2.1.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@floating-ui/dom': 1.7.4 + '@floating-ui/dom': 1.7.6 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@floating-ui/utils@0.2.10': {} + '@floating-ui/utils@0.2.11': {} '@foxglove/crc@0.0.3': {} @@ -11753,13 +12426,13 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@fractalwagmi/solana-wallet-adapter@0.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@fractalwagmi/solana-wallet-adapter@0.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@fractalwagmi/popup-connection': 1.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 5.0.0 transitivePeerDependencies: - - '@solana/web3.js' - react - react-dom @@ -11777,7 +12450,7 @@ snapshots: dependencies: '@harmoniclabs/uint8array-utils': 1.0.4 - '@harmoniclabs/cbor@1.3.0': + '@harmoniclabs/cbor@1.6.0': dependencies: '@harmoniclabs/bytestring': 1.0.0 '@harmoniclabs/obj-utils': 1.0.0 @@ -11792,25 +12465,24 @@ snapshots: '@harmoniclabs/pair@1.0.0': {} - '@harmoniclabs/plutus-data@1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0)': + '@harmoniclabs/plutus-data@1.2.6(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0)': dependencies: '@harmoniclabs/biguint': 1.0.0 '@harmoniclabs/bytestring': 1.0.0 - '@harmoniclabs/cbor': 1.3.0 + '@harmoniclabs/cbor': 1.6.0 '@harmoniclabs/crypto': 0.2.5 '@harmoniclabs/uint8array-utils': 1.0.4 '@harmoniclabs/uint8array-utils@1.0.4': {} - '@harmoniclabs/uplc@1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0)(@harmoniclabs/crypto@0.2.5)(@harmoniclabs/pair@1.0.0)(@harmoniclabs/plutus-data@1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0))': + '@harmoniclabs/uplc@1.4.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0)(@harmoniclabs/crypto@0.2.5)(@harmoniclabs/pair@1.0.0)(@harmoniclabs/plutus-data@1.2.6(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0))': dependencies: '@harmoniclabs/bigint-utils': 1.0.0 - '@harmoniclabs/bitstream': 1.0.0 '@harmoniclabs/bytestring': 1.0.0 - '@harmoniclabs/cbor': 1.3.0 + '@harmoniclabs/cbor': 1.6.0 '@harmoniclabs/crypto': 0.2.5 '@harmoniclabs/pair': 1.0.0 - '@harmoniclabs/plutus-data': 1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0) + '@harmoniclabs/plutus-data': 1.2.6(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0) '@harmoniclabs/uint8array-utils': 1.0.4 '@img/colour@1.1.0': @@ -11898,7 +12570,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.11.1 + '@emnapi/runtime': 1.11.2 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -11910,32 +12582,32 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inquirer/ansi@2.0.5': {} + '@inquirer/ansi@2.0.7': {} - '@inquirer/confirm@6.0.13(@types/node@26.0.0)': + '@inquirer/confirm@6.1.1(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.1.10(@types/node@26.0.0) - '@inquirer/type': 4.0.5(@types/node@26.0.0) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 - '@inquirer/core@11.1.10(@types/node@26.0.0)': + '@inquirer/core@11.2.1(@types/node@26.1.1)': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5(@types/node@26.0.0) + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@26.1.1) cli-width: 4.1.0 - fast-wrap-ansi: 0.2.0 + fast-wrap-ansi: 0.2.2 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 - '@inquirer/figures@2.0.5': {} + '@inquirer/figures@2.0.7': {} - '@inquirer/type@4.0.5(@types/node@26.0.0)': + '@inquirer/type@4.0.7(@types/node@26.1.1)': optionalDependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 '@ioredis/commands@1.5.1': {} @@ -11946,7 +12618,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.14.2 + js-yaml: 3.15.0 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.6': {} @@ -11961,7 +12633,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 26.0.0 + '@types/node': 26.1.1 jest-mock: 29.7.0 '@jest/expect-utils@30.4.1': @@ -11972,7 +12644,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 26.0.0 + '@types/node': 26.1.1 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -11981,7 +12653,7 @@ snapshots: '@jest/pattern@30.4.0': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.1 jest-regex-util: 30.4.0 '@jest/schemas@29.6.3': @@ -11990,11 +12662,11 @@ snapshots: '@jest/schemas@30.4.1': dependencies: - '@sinclair/typebox': 0.34.49 + '@sinclair/typebox': 0.34.50 '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 6.1.1 @@ -12017,7 +12689,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 26.0.0 + '@types/node': 26.1.1 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -12027,7 +12699,7 @@ snapshots: '@jest/schemas': 30.4.1 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.7.0 + '@types/node': 26.1.1 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -12129,12 +12801,12 @@ snapshots: big-integer: 1.6.52 utility-types: 3.11.0 - '@keplr-wallet/wc-client@0.12.313(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(starknet@6.23.1(encoding@0.1.13))': + '@keplr-wallet/wc-client@0.12.313(@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(starknet@6.23.1(encoding@0.1.13))': dependencies: '@keplr-wallet/provider': 0.12.313(starknet@6.23.1(encoding@0.1.13)) '@keplr-wallet/types': 0.12.313(starknet@6.23.1(encoding@0.1.13)) - '@walletconnect/sign-client': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/sign-client': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) buffer: 6.0.3 deepmerge: 4.3.1 long: 5.3.2 @@ -12170,12 +12842,12 @@ snapshots: react-qr-reader: 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) rxjs: 6.6.7 - '@keystonehq/sol-keyring@0.20.0(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@keystonehq/sol-keyring@0.20.0(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: '@keystonehq/bc-ur-registry': 0.5.4 '@keystonehq/bc-ur-registry-sol': 0.9.5 '@keystonehq/sdk': 0.19.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 5.0.0 uuid: 8.3.2 transitivePeerDependencies: @@ -12195,10 +12867,10 @@ snapshots: '@ledgerhq/devices@6.27.1': dependencies: - '@ledgerhq/errors': 6.34.1 + '@ledgerhq/errors': 6.37.0 '@ledgerhq/logs': 6.17.0 rxjs: 6.6.7 - semver: 7.8.0 + semver: 7.8.5 '@ledgerhq/devices@8.14.2': dependencies: @@ -12207,11 +12879,8 @@ snapshots: rxjs: 7.8.2 semver: 7.7.3 - '@ledgerhq/devices@8.15.1': + '@ledgerhq/devices@8.16.0': dependencies: - '@ledgerhq/errors': 6.36.0 - '@ledgerhq/logs': 6.17.0 - rxjs: 7.8.2 semver: 7.7.3 '@ledgerhq/devices@8.5.0': @@ -12219,28 +12888,28 @@ snapshots: '@ledgerhq/errors': 6.34.1 '@ledgerhq/logs': 6.17.0 rxjs: 7.8.2 - semver: 7.8.0 + semver: 7.8.5 '@ledgerhq/devices@8.7.0': dependencies: '@ledgerhq/errors': 6.34.1 '@ledgerhq/logs': 6.17.0 rxjs: 7.8.2 - semver: 7.8.0 + semver: 7.8.5 '@ledgerhq/errors@6.34.1': {} - '@ledgerhq/errors@6.36.0': {} + '@ledgerhq/errors@6.37.0': {} '@ledgerhq/hw-app-trx@6.29.2': dependencies: - '@ledgerhq/hw-transport': 6.35.2 + '@ledgerhq/hw-transport': 6.35.5 '@ledgerhq/hw-transport-webhid@6.27.1': dependencies: '@ledgerhq/devices': 6.27.1 - '@ledgerhq/errors': 6.34.1 - '@ledgerhq/hw-transport': 6.35.2 + '@ledgerhq/errors': 6.37.0 + '@ledgerhq/hw-transport': 6.35.5 '@ledgerhq/logs': 6.17.0 '@ledgerhq/hw-transport-webhid@6.35.2': @@ -12260,7 +12929,7 @@ snapshots: '@ledgerhq/hw-transport@6.27.1': dependencies: '@ledgerhq/devices': 6.27.1 - '@ledgerhq/errors': 6.34.1 + '@ledgerhq/errors': 6.37.0 events: 3.3.0 '@ledgerhq/hw-transport@6.31.13': @@ -12284,27 +12953,27 @@ snapshots: '@ledgerhq/logs': 6.17.0 events: 3.3.0 - '@ledgerhq/hw-transport@6.35.4': + '@ledgerhq/hw-transport@6.35.5': dependencies: - '@ledgerhq/devices': 8.15.1 - '@ledgerhq/errors': 6.36.0 + '@ledgerhq/devices': 8.16.0 + '@ledgerhq/errors': 6.37.0 '@ledgerhq/logs': 6.17.0 events: 3.3.0 '@ledgerhq/logs@6.17.0': {} - '@ledgerhq/wallet-api-client@1.14.5(@ton/crypto@3.3.0)(encoding@0.1.13)': + '@ledgerhq/wallet-api-client@1.15.1(@ton/crypto@3.3.0)(encoding@0.1.13)': dependencies: - '@ledgerhq/hw-transport': 6.35.4 - '@ledgerhq/wallet-api-core': 1.32.1(@ton/crypto@3.3.0)(encoding@0.1.13) + '@ledgerhq/hw-transport': 6.35.5 + '@ledgerhq/wallet-api-core': 1.34.0(@ton/crypto@3.3.0)(encoding@0.1.13) bignumber.js: 9.3.1 transitivePeerDependencies: - '@ton/crypto' - encoding - '@ledgerhq/wallet-api-core@1.32.1(@ton/crypto@3.3.0)(encoding@0.1.13)': + '@ledgerhq/wallet-api-core@1.34.0(@ton/crypto@3.3.0)(encoding@0.1.13)': dependencies: - '@ledgerhq/errors': 6.36.0 + '@ledgerhq/errors': 6.37.0 '@stacks/transactions': 6.17.0(encoding@0.1.13) '@ton/core': 0.62.1(@ton/crypto@3.3.0) bignumber.js: 9.3.1 @@ -12315,7 +12984,16 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@lit-labs/ssr-dom-shim@1.5.1': {} + '@libp2p/interface@3.2.4': + dependencies: + '@multiformats/dns': 1.0.15 + '@multiformats/multiaddr': 13.0.3 + main-event: 1.0.4 + multiformats: 14.0.4 + progress-events: 1.1.0 + uint8arraylist: 3.0.2 + + '@lit-labs/ssr-dom-shim@1.6.0': {} '@lit/react@1.0.8(@types/react@19.2.17)': dependencies: @@ -12324,19 +13002,19 @@ snapshots: '@lit/reactive-element@1.6.3': dependencies: - '@lit-labs/ssr-dom-shim': 1.5.1 + '@lit-labs/ssr-dom-shim': 1.6.0 '@lit/reactive-element@2.1.2': dependencies: - '@lit-labs/ssr-dom-shim': 1.5.1 + '@lit-labs/ssr-dom-shim': 1.6.0 - '@luno-kit/core@0.0.13(@dedot/chaintypes@0.123.0(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@luno-kit/core@0.0.13(@dedot/chaintypes@0.123.0(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@dedot/chaintypes': 0.123.0(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@ledgerhq/hw-transport-webusb': 6.29.13 '@mimirdev/apps-inject': 3.2.0 '@polkadot-api/pjs-signer': 0.6.14 - '@walletconnect/universal-provider': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@zondax/ledger-substrate': 2.0.0 buffer: 6.0.3 dedot: 0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -12368,7 +13046,7 @@ snapshots: - utf-8-validate - zod - '@meshsdk/common@1.9.0-beta-40': + '@meshsdk/common@1.9.1': dependencies: bech32: 2.0.0 bip39: 3.1.0 @@ -12377,22 +13055,23 @@ snapshots: transitivePeerDependencies: - react-native-b4a - '@meshsdk/core-cst@1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@meshsdk/core-cst@1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': dependencies: - '@cardano-sdk/core': 0.45.10(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/crypto': 0.2.3 - '@cardano-sdk/input-selection': 0.13.34(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/util': 0.15.7 - '@harmoniclabs/cbor': 1.3.0 + '@cardano-sdk/core': 0.46.12(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@cardano-sdk/crypto': 0.4.5 + '@cardano-sdk/input-selection': 0.14.28(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@cardano-sdk/util': 0.17.1 + '@harmoniclabs/cbor': 1.6.0 '@harmoniclabs/pair': 1.0.0 - '@harmoniclabs/plutus-data': 1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0) - '@harmoniclabs/uplc': 1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0)(@harmoniclabs/crypto@0.2.5)(@harmoniclabs/pair@1.0.0)(@harmoniclabs/plutus-data@1.2.4(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.3.0)) - '@meshsdk/common': 1.9.0-beta-40 + '@harmoniclabs/plutus-data': 1.2.6(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0) + '@harmoniclabs/uplc': 1.4.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0)(@harmoniclabs/crypto@0.2.5)(@harmoniclabs/pair@1.0.0)(@harmoniclabs/plutus-data@1.2.6(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/cbor@1.6.0)) + '@meshsdk/common': 1.9.1 '@types/base32-encoding': 1.0.2 base32-encoding: 1.0.0 bech32: 2.0.0 blakejs: 1.2.1 - bn.js: 5.2.3 + bn.js: 5.2.4 + hash.js: 1.1.7 transitivePeerDependencies: - '@dcspark/cardano-multiplatform-lib-asmjs' - '@dcspark/cardano-multiplatform-lib-browser' @@ -12405,13 +13084,13 @@ snapshots: - rxjs - utf-8-validate - '@meshsdk/transaction@1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@meshsdk/transaction@1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': dependencies: - '@cardano-sdk/core': 0.45.10(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/input-selection': 0.13.34(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@cardano-sdk/util': 0.15.7 - '@meshsdk/common': 1.9.0-beta-40 - '@meshsdk/core-cst': 1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@cardano-sdk/core': 0.46.12(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@cardano-sdk/input-selection': 0.14.28(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@cardano-sdk/util': 0.17.1 + '@meshsdk/common': 1.9.1 + '@meshsdk/core-cst': 1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) json-bigint: 1.0.0 transitivePeerDependencies: - '@dcspark/cardano-multiplatform-lib-asmjs' @@ -12425,12 +13104,12 @@ snapshots: - rxjs - utf-8-validate - '@meshsdk/wallet@1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': + '@meshsdk/wallet@1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10)': dependencies: - '@meshsdk/common': 1.9.0-beta-40 - '@meshsdk/core-cst': 1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@meshsdk/transaction': 1.9.0-beta-40(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) - '@simplewebauthn/browser': 13.2.2 + '@meshsdk/common': 1.9.1 + '@meshsdk/core-cst': 1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@meshsdk/transaction': 1.9.1(@harmoniclabs/bytestring@1.0.0)(@harmoniclabs/crypto@0.2.5)(bufferutil@4.0.9)(encoding@0.1.13)(rxjs@7.8.2)(utf-8-validate@5.0.10) + '@simplewebauthn/browser': 13.3.0 transitivePeerDependencies: - '@dcspark/cardano-multiplatform-lib-asmjs' - '@dcspark/cardano-multiplatform-lib-browser' @@ -12475,29 +13154,31 @@ snapshots: dependencies: eventemitter3: 5.0.4 - '@mixpanel/rrdom@2.0.0-alpha.18.4': + '@mixpanel/rrdom@2.0.0-alpha.18.5': dependencies: - '@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.4 + '@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.5 '@mixpanel/rrweb-plugin-console-record@2.0.0-alpha.18.4(@mixpanel/rrweb-utils@2.0.0-alpha.18.4)(@mixpanel/rrweb@2.0.0-alpha.18.4)': dependencies: '@mixpanel/rrweb': 2.0.0-alpha.18.4 '@mixpanel/rrweb-utils': 2.0.0-alpha.18.4 - '@mixpanel/rrweb-snapshot@2.0.0-alpha.18.4': + '@mixpanel/rrweb-snapshot@2.0.0-alpha.18.5': dependencies: - postcss: 8.5.14 + postcss: 8.5.16 - '@mixpanel/rrweb-types@2.0.0-alpha.18.4': {} + '@mixpanel/rrweb-types@2.0.0-alpha.18.5': {} '@mixpanel/rrweb-utils@2.0.0-alpha.18.4': {} + '@mixpanel/rrweb-utils@2.0.0-alpha.18.5': {} + '@mixpanel/rrweb@2.0.0-alpha.18.4': dependencies: - '@mixpanel/rrdom': 2.0.0-alpha.18.4 - '@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.4 - '@mixpanel/rrweb-types': 2.0.0-alpha.18.4 - '@mixpanel/rrweb-utils': 2.0.0-alpha.18.4 + '@mixpanel/rrdom': 2.0.0-alpha.18.5 + '@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.5 + '@mixpanel/rrweb-types': 2.0.0-alpha.18.5 + '@mixpanel/rrweb-utils': 2.0.0-alpha.18.5 '@types/css-font-loading-module': 0.0.7 '@xstate/fsm': 1.6.5 base64-arraybuffer: 1.0.2 @@ -12554,22 +13235,22 @@ snapshots: '@msgpack/msgpack@3.1.3': {} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': optional: true '@mswjs/interceptors@0.41.9': @@ -12581,14 +13262,14 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@multiformats/dns@1.0.10': + '@multiformats/dns@1.0.15': dependencies: - buffer: 6.0.3 - dns-packet: 5.6.1 + '@dnsquery/dns-packet': 6.1.1 + '@libp2p/interface': 3.2.4 hashlru: 2.3.0 - p-queue: 9.0.0 - progress-events: 1.0.1 - uint8arrays: 5.1.0 + p-queue: 9.3.1 + progress-events: 1.1.0 + uint8arrays: 6.1.1 '@multiformats/mafmt@12.1.6': dependencies: @@ -12598,43 +13279,57 @@ snapshots: dependencies: '@chainsafe/is-ip': 2.1.0 '@chainsafe/netmask': 2.0.0 - '@multiformats/dns': 1.0.10 - abort-error: 1.0.1 - multiformats: 13.4.1 - uint8-varint: 2.0.4 - uint8arrays: 5.1.0 + '@multiformats/dns': 1.0.15 + abort-error: 1.0.2 + multiformats: 13.4.2 + uint8-varint: 2.0.5 + uint8arrays: 5.1.1 + + '@multiformats/multiaddr@13.0.3': + dependencies: + '@chainsafe/is-ip': 2.1.0 + multiformats: 14.0.4 + uint8-varint: 3.0.0 + uint8arrays: 6.1.1 + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.3 + optional: true - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@16.2.9': {} + '@next/env@16.2.10': {} - '@next/swc-darwin-arm64@16.2.9': + '@next/swc-darwin-arm64@16.2.10': optional: true - '@next/swc-darwin-x64@16.2.9': + '@next/swc-darwin-x64@16.2.10': optional: true - '@next/swc-linux-arm64-gnu@16.2.9': + '@next/swc-linux-arm64-gnu@16.2.10': optional: true - '@next/swc-linux-arm64-musl@16.2.9': + '@next/swc-linux-arm64-musl@16.2.10': optional: true - '@next/swc-linux-x64-gnu@16.2.9': + '@next/swc-linux-x64-gnu@16.2.10': optional: true - '@next/swc-linux-x64-musl@16.2.9': + '@next/swc-linux-x64-musl@16.2.10': optional: true - '@next/swc-win32-arm64-msvc@16.2.9': + '@next/swc-win32-arm64-msvc@16.2.10': optional: true - '@next/swc-win32-x64-msvc@16.2.9': + '@next/swc-win32-x64-msvc@16.2.10': optional: true '@ngraveio/bc-ur@1.1.13': @@ -12655,68 +13350,195 @@ snapshots: dependencies: '@noble/hashes': 1.3.2 - '@noble/curves@1.4.2': - dependencies: - '@noble/hashes': 1.4.0 + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.0 + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/curves@1.8.0': + dependencies: + '@noble/hashes': 1.7.0 + + '@noble/curves@1.8.1': + dependencies: + '@noble/hashes': 1.7.1 + + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/curves@1.9.2': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/curves@2.2.0': + dependencies: + '@noble/hashes': 2.2.0 + + '@noble/hashes@1.1.5': {} + + '@noble/hashes@1.3.2': {} + + '@noble/hashes@1.4.0': {} + + '@noble/hashes@1.6.0': {} + + '@noble/hashes@1.7.0': {} + + '@noble/hashes@1.7.1': {} + + '@noble/hashes@1.8.0': {} + + '@noble/hashes@2.2.0': {} + + '@noble/secp256k1@1.7.1': {} + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@oxc-parser/binding-android-arm-eabi@0.137.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.137.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.137.0': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + optional: true - '@noble/curves@1.7.0': - dependencies: - '@noble/hashes': 1.6.0 + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + optional: true - '@noble/curves@1.8.0': - dependencies: - '@noble/hashes': 1.7.0 + '@oxc-project/types@0.137.0': {} - '@noble/curves@1.8.1': - dependencies: - '@noble/hashes': 1.7.1 + '@oxc-project/types@0.139.0': {} - '@noble/curves@1.9.1': - dependencies: - '@noble/hashes': 1.8.0 + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + optional: true - '@noble/curves@1.9.2': - dependencies: - '@noble/hashes': 1.8.0 + '@oxc-resolver/binding-android-arm64@11.21.3': + optional: true - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 + '@oxc-resolver/binding-darwin-arm64@11.21.3': + optional: true - '@noble/curves@2.2.0': - dependencies: - '@noble/hashes': 2.2.0 + '@oxc-resolver/binding-darwin-x64@11.21.3': + optional: true - '@noble/hashes@1.1.5': {} + '@oxc-resolver/binding-freebsd-x64@11.21.3': + optional: true - '@noble/hashes@1.3.2': {} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + optional: true - '@noble/hashes@1.4.0': {} + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + optional: true - '@noble/hashes@1.6.0': {} + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + optional: true - '@noble/hashes@1.7.0': {} + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + optional: true - '@noble/hashes@1.7.1': {} + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + optional: true - '@noble/hashes@1.8.0': {} + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + optional: true - '@noble/hashes@2.2.0': {} + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + optional: true - '@noble/secp256k1@1.7.1': {} + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + optional: true - '@open-draft/deferred-promise@2.2.0': {} + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + optional: true - '@open-draft/deferred-promise@3.0.0': {} + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + optional: true - '@open-draft/logger@0.3.0': + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': dependencies: - is-node-process: 1.2.0 - outvariant: 1.4.3 + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true - '@open-draft/until@2.1.0': {} + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + optional: true - '@oxc-project/types@0.133.0': {} + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + optional: true '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -12762,7 +13584,7 @@ snapshots: detect-libc: 2.1.2 is-glob: 4.0.3 node-addon-api: 7.1.1 - picomatch: 4.0.4 + picomatch: 4.0.5 optionalDependencies: '@parcel/watcher-android-arm64': 2.5.6 '@parcel/watcher-darwin-arm64': 2.5.6 @@ -12799,12 +13621,16 @@ snapshots: crypto-js: 4.2.0 uuidv4: 6.2.13 - '@particle-network/solana-wallet@1.3.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)': + '@particle-network/solana-wallet@1.3.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)': dependencies: '@particle-network/auth': 1.3.1 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 5.0.0 + '@phosphor-icons/webcomponents@2.1.5': + dependencies: + lit: 3.3.3 + '@polka/url@1.0.0-next.29': {} '@polkadot-api/metadata-builders@0.13.4': @@ -12994,9 +13820,9 @@ snapshots: - bufferutil - utf-8-validate - '@project-serum/sol-wallet-adapter@0.2.6(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@project-serum/sol-wallet-adapter@0.2.6(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 4.0.1 eventemitter3: 4.0.7 @@ -13023,333 +13849,308 @@ snapshots: '@protobufjs/utf8@1.1.1': {} - '@radix-ui/primitive@1.1.3': {} + '@radix-ui/primitive@1.1.5': {} - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-arrow@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-collection@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-context@1.1.2(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dialog@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) aria-hidden: 1.2.6 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.1(@types/react@19.2.17)(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-direction@1.1.1(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-direction@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-dismissable-layer@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-dropdown-menu@2.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-menu': 2.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-focus-scope@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-id@1.1.1(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-menu@2.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-roving-focus': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) aria-hidden: 1.2.6 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.1(@types/react@19.2.17)(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/rect': 1.1.1 + '@radix-ui/react-popper@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-arrow': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/rect': 1.1.2 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-portal@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-presence@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-roving-focus@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-slot@1.2.4(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-tooltip@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-visually-hidden': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/rect': 1.1.1 react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-use-rect@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/rect': 1.1.2 react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-use-size@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-visually-hidden@1.2.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-visually-hidden@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/rect@1.1.1': {} + '@radix-ui/rect@1.1.2': {} - '@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))': + '@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))': dependencies: merge-options: 3.0.4 - react-native: 0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) + react-native: 0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) optional: true '@react-native/assets-registry@0.81.1': {} - '@react-native/codegen@0.81.1(@babel/core@7.29.0)': + '@react-native/codegen@0.81.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 glob: 7.2.3 hermes-parser: 0.29.1 @@ -13397,12 +14198,12 @@ snapshots: '@react-native/normalize-colors@0.81.1': {} - '@react-native/virtualized-lists@0.81.1(@types/react@19.2.17)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': + '@react-native/virtualized-lists@0.81.1(@types/react@19.2.17)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 19.2.7 - react-native: 0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) + react-native: 0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) optionalDependencies: '@types/react': 19.2.17 @@ -13410,89 +14211,89 @@ snapshots: dependencies: '@standard-schema/spec': 1.1.0 '@standard-schema/utils': 0.3.0 - immer: 11.1.8 + immer: 11.1.11 redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.1 + reselect: 5.2.0 optionalDependencies: react: 19.2.7 react-redux: 9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) - '@reown/appkit-common@1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4)': + '@reown/appkit-common@1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - '@reown/appkit-common@1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-common@1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - '@reown/appkit-common@1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4)': + '@reown/appkit-common@1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod + optional: true - '@reown/appkit-common@1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-common@1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod + optional: true - '@reown/appkit-common@1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4)': + '@reown/appkit-common@1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - optional: true - '@reown/appkit-common@1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-common@1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - optional: true - '@reown/appkit-controllers@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-controllers@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13521,13 +14322,13 @@ snapshots: - utf-8-validate - zod - '@reown/appkit-controllers@1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-controllers@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-wallet': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - valtio: 2.1.5(@types/react@19.2.17)(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13555,14 +14356,15 @@ snapshots: - uploadthing - utf-8-validate - zod + optional: true - '@reown/appkit-controllers@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-controllers@1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) - viem: 2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-wallet': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + valtio: 2.1.7(@types/react@19.2.17)(react@19.2.7) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13590,16 +14392,15 @@ snapshots: - uploadthing - utf-8-validate - zod - optional: true - '@reown/appkit-pay@1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-pay@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-ui': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@2.1.5(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) lit: 3.3.0 - valtio: 2.1.5(@types/react@19.2.17)(react@19.2.7) + valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13627,15 +14428,16 @@ snapshots: - uploadthing - utf-8-validate - zod + optional: true - '@reown/appkit-pay@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-pay@1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-ui': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(valtio@2.1.7(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) lit: 3.3.0 - valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) + valtio: 2.1.7(@types/react@19.2.17)(react@19.2.7) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13656,35 +14458,38 @@ snapshots: - aws4fetch - bufferutil - db0 + - debug - encoding + - fastestsmallesttextencoderdecoder + - immer - ioredis - react - typescript - uploadthing + - use-sync-external-store - utf-8-validate - zod - optional: true '@reown/appkit-polyfills@1.7.2': dependencies: buffer: 6.0.3 - '@reown/appkit-polyfills@1.7.20': + '@reown/appkit-polyfills@1.7.8': dependencies: buffer: 6.0.3 + optional: true - '@reown/appkit-polyfills@1.7.8': + '@reown/appkit-polyfills@1.8.20': dependencies: buffer: 6.0.3 - optional: true - '@reown/appkit-scaffold-ui@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': + '@reown/appkit-scaffold-ui@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-ui': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-ui': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) lit: 3.1.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -13715,13 +14520,13 @@ snapshots: - valtio - zod - '@reown/appkit-scaffold-ui@1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@2.1.5(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': + '@reown/appkit-scaffold-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-ui': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@2.1.5(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-wallet': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) lit: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -13751,14 +14556,16 @@ snapshots: - utf-8-validate - valtio - zod + optional: true - '@reown/appkit-scaffold-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': + '@reown/appkit-scaffold-ui@1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(valtio@2.1.7(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-pay': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-ui': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(valtio@2.1.7(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-wallet': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) lit: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -13780,21 +14587,24 @@ snapshots: - aws4fetch - bufferutil - db0 + - debug - encoding + - fastestsmallesttextencoderdecoder + - immer - ioredis - react - typescript - uploadthing + - use-sync-external-store - utf-8-validate - valtio - zod - optional: true - '@reown/appkit-ui@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-ui@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) lit: 3.1.0 qrcode: 1.5.3 transitivePeerDependencies: @@ -13825,11 +14635,11 @@ snapshots: - utf-8-validate - zod - '@reown/appkit-ui@1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-wallet': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) lit: 3.3.0 qrcode: 1.5.3 transitivePeerDependencies: @@ -13859,12 +14669,14 @@ snapshots: - uploadthing - utf-8-validate - zod + optional: true - '@reown/appkit-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit-ui@1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@phosphor-icons/webcomponents': 2.1.5 + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-wallet': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) lit: 3.3.0 qrcode: 1.5.3 transitivePeerDependencies: @@ -13894,18 +14706,17 @@ snapshots: - uploadthing - utf-8-validate - zod - optional: true - '@reown/appkit-utils@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': + '@reown/appkit-utils@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@reown/appkit-polyfills': 1.7.2 - '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13934,17 +14745,16 @@ snapshots: - utf-8-validate - zod - '@reown/appkit-utils@1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@2.1.5(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': + '@reown/appkit-utils@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-polyfills': 1.7.20 - '@reown/appkit-wallet': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@wallet-standard/wallet': 1.1.0 + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-polyfills': 1.7.8 + '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - valtio: 2.1.5(@types/react@19.2.17)(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13972,17 +14782,24 @@ snapshots: - uploadthing - utf-8-validate - zod + optional: true - '@reown/appkit-utils@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': + '@reown/appkit-utils@1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(valtio@2.1.7(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-polyfills': 1.7.8 - '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) - viem: 2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-polyfills': 1.8.20 + '@reown/appkit-wallet': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@wallet-standard/wallet': 1.1.0 + '@walletconnect/logger': 3.0.2 + '@walletconnect/universal-provider': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + valtio: 2.1.7(@types/react@19.2.17)(react@19.2.7) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + optionalDependencies: + '@base-org/account': 2.4.0(@types/react@19.2.17)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) + '@coinbase/wallet-sdk': 4.3.6(@types/react@19.2.17)(bufferutil@4.0.9)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) + '@safe-global/safe-apps-provider': 0.18.6(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -14003,18 +14820,21 @@ snapshots: - aws4fetch - bufferutil - db0 + - debug - encoding + - fastestsmallesttextencoderdecoder + - immer - ioredis - react - typescript - uploadthing + - use-sync-external-store - utf-8-validate - zod - optional: true - '@reown/appkit-wallet@1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@reown/appkit-wallet@1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: - '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4) + '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4) '@reown/appkit-polyfills': 1.7.2 '@walletconnect/logger': 2.1.2 zod: 3.22.4 @@ -14023,43 +14843,43 @@ snapshots: - typescript - utf-8-validate - '@reown/appkit-wallet@1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@reown/appkit-wallet@1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4) - '@reown/appkit-polyfills': 1.7.20 + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4) + '@reown/appkit-polyfills': 1.7.8 '@walletconnect/logger': 2.1.2 zod: 3.22.4 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate + optional: true - '@reown/appkit-wallet@1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@reown/appkit-wallet@1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4) - '@reown/appkit-polyfills': 1.7.8 - '@walletconnect/logger': 2.1.2 + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.22.4) + '@reown/appkit-polyfills': 1.8.20 + '@walletconnect/logger': 3.0.2 zod: 3.22.4 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - optional: true - '@reown/appkit@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit@1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@reown/appkit-polyfills': 1.7.2 - '@reown/appkit-scaffold-ui': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-ui': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/universal-provider': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-scaffold-ui': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-ui': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-wallet': 1.7.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/universal-provider': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -14088,23 +14908,21 @@ snapshots: - utf-8-validate - zod - '@reown/appkit@1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': - dependencies: - '@reown/appkit-common': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-pay': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-polyfills': 1.7.20 - '@reown/appkit-scaffold-ui': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@2.1.5(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-ui': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@2.1.5(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-wallet': 1.7.20(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-pay': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@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.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 - semver: 7.7.2 - valtio: 2.1.5(@types/react@19.2.17)(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - optionalDependencies: - '@lit/react': 1.0.8(@types/react@19.2.17) + valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -14132,22 +14950,25 @@ snapshots: - uploadthing - utf-8-validate - zod + optional: true - '@reown/appkit@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@reown/appkit@1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-pay': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) - '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-common': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-controllers': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-pay': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-polyfills': 1.8.20 + '@reown/appkit-scaffold-ui': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(valtio@2.1.7(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-ui': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit-utils': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(valtio@2.1.7(@types/react@19.2.17)(react@19.2.7))(zod@3.25.76) + '@reown/appkit-wallet': 1.8.20(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 - valtio: 1.13.2(@types/react@19.2.17)(react@19.2.7) - viem: 2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + semver: 7.7.2 + valtio: 2.1.7(@types/react@19.2.17)(react@19.2.7) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + optionalDependencies: + '@lit/react': 1.0.8(@types/react@19.2.17) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -14168,81 +14989,84 @@ snapshots: - aws4fetch - bufferutil - db0 + - debug - encoding + - fastestsmallesttextencoderdecoder + - immer - ioredis - react - typescript - uploadthing + - use-sync-external-store - utf-8-validate - zod - optional: true - '@rev-dep/darwin-arm64@2.15.0': + '@rev-dep/darwin-arm64@2.18.0': optional: true - '@rev-dep/linux-x64@2.15.0': + '@rev-dep/linux-x64@2.18.0': optional: true - '@rev-dep/win32-x64@2.15.0': + '@rev-dep/win32-x64@2.18.0': optional: true - '@rolldown/binding-android-arm64@1.0.3': + '@rolldown/binding-android-arm64@1.1.5': optional: true - '@rolldown/binding-darwin-arm64@1.0.3': + '@rolldown/binding-darwin-arm64@1.1.5': optional: true - '@rolldown/binding-darwin-x64@1.0.3': + '@rolldown/binding-darwin-x64@1.1.5': optional: true - '@rolldown/binding-freebsd-x64@1.0.3': + '@rolldown/binding-freebsd-x64@1.1.5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.3': + '@rolldown/binding-linux-arm64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.3': + '@rolldown/binding-linux-arm64-musl@1.1.5': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.3': + '@rolldown/binding-linux-ppc64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.3': + '@rolldown/binding-linux-s390x-gnu@1.1.5': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.3': + '@rolldown/binding-linux-x64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-x64-musl@1.0.3': + '@rolldown/binding-linux-x64-musl@1.1.5': optional: true - '@rolldown/binding-openharmony-arm64@1.0.3': + '@rolldown/binding-openharmony-arm64@1.1.5': optional: true - '@rolldown/binding-wasm32-wasi@1.0.3': + '@rolldown/binding-wasm32-wasi@1.1.5': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.3': + '@rolldown/binding-win32-arm64-msvc@1.1.5': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.3': + '@rolldown/binding-win32-x64-msvc@1.1.5': optional: true - '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))': dependencies: - '@babel/core': 7.29.0 - picomatch: 4.0.4 - rolldown: 1.0.3 + '@babel/core': 7.29.7 + picomatch: 4.0.5 + rolldown: 1.1.5 optionalDependencies: - '@babel/runtime': 7.29.2 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + '@babel/runtime': 7.29.7 + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) '@rolldown/pluginutils@1.0.1': {} @@ -14258,7 +15082,7 @@ snapshots: dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.4 + picomatch: 4.0.5 optionalDependencies: rollup: 4.53.3 @@ -14328,9 +15152,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@safe-global/safe-apps-provider@0.18.6(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@safe-global/safe-apps-provider@0.18.6(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - bufferutil @@ -14338,10 +15162,10 @@ snapshots: - utf-8-validate - zod - '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.22.9 - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - bufferutil - typescript @@ -14401,19 +15225,19 @@ snapshots: '@noble/curves': 1.7.0 '@noble/hashes': 1.6.0 - '@simple-libs/child-process-utils@1.0.2': + '@simple-libs/child-process-utils@2.0.0': dependencies: - '@simple-libs/stream-utils': 1.2.0 + '@simple-libs/stream-utils': 2.0.0 - '@simple-libs/stream-utils@1.2.0': {} + '@simple-libs/stream-utils@2.0.0': {} - '@simplewebauthn/browser@13.2.2': {} + '@simplewebauthn/browser@13.3.0': {} '@sinclair/typebox@0.27.10': {} '@sinclair/typebox@0.33.22': {} - '@sinclair/typebox@0.34.49': {} + '@sinclair/typebox@0.34.50': {} '@sinonjs/commons@3.0.1': dependencies: @@ -14425,10 +15249,10 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@solana-mobile/mobile-wallet-adapter-protocol-web3js@2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': + '@solana-mobile/mobile-wallet-adapter-protocol-web3js@2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': dependencies: - '@solana-mobile/mobile-wallet-adapter-protocol': 2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana-mobile/mobile-wallet-adapter-protocol': 2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 5.0.0 js-base64: 3.7.8 transitivePeerDependencies: @@ -14436,36 +15260,36 @@ snapshots: - react - react-native - '@solana-mobile/mobile-wallet-adapter-protocol@2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': + '@solana-mobile/mobile-wallet-adapter-protocol@2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': dependencies: - '@solana/wallet-standard': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) + '@solana/wallet-standard': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) '@solana/wallet-standard-util': 1.1.2 '@wallet-standard/core': 1.1.1 js-base64: 3.7.8 - react-native: 0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) + react-native: 0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) transitivePeerDependencies: - '@solana/wallet-adapter-base' - '@solana/web3.js' - bs58 - react - '@solana-mobile/wallet-adapter-mobile@2.2.3(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': + '@solana-mobile/wallet-adapter-mobile@2.2.3(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': dependencies: - '@solana-mobile/mobile-wallet-adapter-protocol-web3js': 2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) - '@solana-mobile/wallet-standard-mobile': 0.4.1(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) + '@solana-mobile/mobile-wallet-adapter-protocol-web3js': 2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + '@solana-mobile/wallet-standard-mobile': 0.4.1(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) '@solana/wallet-standard-features': 1.3.0 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) js-base64: 3.7.8 optionalDependencies: - '@react-native-async-storage/async-storage': 1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)) + '@react-native-async-storage/async-storage': 1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)) transitivePeerDependencies: - react - react-native - '@solana-mobile/wallet-standard-mobile@0.4.1(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': + '@solana-mobile/wallet-standard-mobile@0.4.1(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': dependencies: - '@solana-mobile/mobile-wallet-adapter-protocol': 2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + '@solana-mobile/mobile-wallet-adapter-protocol': 2.2.3(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) '@solana/wallet-standard-chains': 1.1.1 '@solana/wallet-standard-features': 1.3.0 '@wallet-standard/base': 1.1.0 @@ -14479,575 +15303,916 @@ snapshots: - react - react-native - '@solana-program/compute-budget@0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@solana-program/compute-budget@0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + dependencies: + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + + '@solana-program/stake@0.2.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + dependencies: + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + + '@solana-program/system@0.10.0(@solana/kit@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/kit': 5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + optional: true - '@solana-program/stake@0.2.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@solana-program/system@0.7.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/system@0.7.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@solana-program/token-2022@0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))': dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/sysvars': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana-program/token-2022@0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))': + '@solana-program/token@0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/sysvars': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/token@0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@solana-program/token@0.9.0(@solana/kit@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/kit': 5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + optional: true - '@solana/accounts@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/accounts@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec': 2.3.0(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec': 2.3.0(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/accounts@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/accounts@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - '@solana/rpc-spec': 3.0.3(typescript@6.0.3) - '@solana/rpc-types': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec': 5.5.1(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/addresses@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/addresses@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/assertions': 2.3.0(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/assertions': 2.3.0(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/addresses@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/addresses@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/assertions': 3.0.3(typescript@6.0.3) - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - '@solana/nominal-types': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/assertions': 5.5.1(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/assertions@2.3.0(typescript@6.0.3)': + '@solana/assertions@2.3.0(typescript@7.0.2)': dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 - '@solana/assertions@3.0.3(typescript@6.0.3)': + '@solana/assertions@5.5.1(typescript@7.0.2)': dependencies: - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 '@solana/buffer-layout@4.0.1': dependencies: buffer: 6.0.3 - '@solana/codecs-core@2.3.0(typescript@6.0.3)': + '@solana/codecs-core@2.3.0(typescript@7.0.2)': + dependencies: + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + + '@solana/codecs-core@5.5.1(typescript@7.0.2)': + dependencies: + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + + '@solana/codecs-data-structures@2.3.0(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + + '@solana/codecs-data-structures@5.5.1(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + + '@solana/codecs-numbers@2.3.0(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + + '@solana/codecs-numbers@5.5.1(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + + '@solana/codecs-strings@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 7.0.2 + + '@solana/codecs-strings@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 7.0.2 + + '@solana/codecs@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-data-structures': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/options': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/codecs@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-data-structures': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/options': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/errors@2.3.0(typescript@7.0.2)': + dependencies: + chalk: 5.6.2 + commander: 14.0.0 + typescript: 7.0.2 + + '@solana/errors@5.5.1(typescript@7.0.2)': + dependencies: + chalk: 5.6.2 + commander: 14.0.2 + optionalDependencies: + typescript: 7.0.2 + + '@solana/fast-stable-stringify@2.3.0(typescript@7.0.2)': + dependencies: + typescript: 7.0.2 + + '@solana/fast-stable-stringify@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 + optional: true + + '@solana/functional@2.3.0(typescript@7.0.2)': + dependencies: + typescript: 7.0.2 + + '@solana/functional@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 + optional: true + + '@solana/instruction-plans@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/instructions': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/promises': 5.5.1(typescript@7.0.2) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + optional: true + + '@solana/instructions@2.3.0(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + + '@solana/instructions@5.5.1(typescript@7.0.2)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + optional: true + + '@solana/keys@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/assertions': 2.3.0(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/keys@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/assertions': 5.5.1(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + optional: true + + '@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + dependencies: + '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/instructions': 2.3.0(typescript@7.0.2) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/programs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-parsed-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/signers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/sysvars': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - ws + + '@solana/kit@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/accounts': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/instruction-plans': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/instructions': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/offchain-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/plugin-core': 5.5.1(typescript@7.0.2) + '@solana/programs': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-api': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-parsed-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-subscriptions': 5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/signers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/sysvars': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-confirmation': 5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + optional: true - '@solana/codecs-core@3.0.3(typescript@6.0.3)': + '@solana/nominal-types@2.3.0(typescript@7.0.2)': dependencies: - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + typescript: 7.0.2 - '@solana/codecs-data-structures@2.3.0(typescript@6.0.3)': + '@solana/nominal-types@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 + + '@solana/offchain-messages@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-data-structures': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + optional: true + + '@solana/options@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-data-structures': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder - '@solana/codecs-data-structures@3.0.3(typescript@6.0.3)': + '@solana/options@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-numbers': 3.0.3(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-data-structures': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/plugin-core@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 + optional: true - '@solana/codecs-numbers@2.3.0(typescript@6.0.3)': + '@solana/programs@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder - '@solana/codecs-numbers@3.0.3(typescript@6.0.3)': + '@solana/programs@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + optional: true - '@solana/codecs-strings@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/promises@2.3.0(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - fastestsmallesttextencoderdecoder: 1.0.22 - typescript: 6.0.3 + typescript: 7.0.2 - '@solana/codecs-strings@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-numbers': 3.0.3(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - fastestsmallesttextencoderdecoder: 1.0.22 - typescript: 6.0.3 + '@solana/promises@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 + optional: true - '@solana/codecs@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/rpc-api@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-data-structures': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/options': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-parsed-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec': 2.3.0(typescript@7.0.2) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/codecs@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-data-structures': 3.0.3(typescript@6.0.3) - '@solana/codecs-numbers': 3.0.3(typescript@6.0.3) - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/options': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/rpc-api@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-parsed-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec': 5.5.1(typescript@7.0.2) + '@solana/rpc-transformers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + optional: true - '@solana/errors@2.3.0(typescript@6.0.3)': + '@solana/rpc-parsed-types@2.3.0(typescript@7.0.2)': dependencies: - chalk: 5.6.2 - commander: 14.0.0 - typescript: 6.0.3 + typescript: 7.0.2 - '@solana/errors@3.0.3(typescript@6.0.3)': - dependencies: - chalk: 5.6.2 - commander: 14.0.0 - typescript: 6.0.3 + '@solana/rpc-parsed-types@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 + optional: true - '@solana/fast-stable-stringify@2.3.0(typescript@6.0.3)': + '@solana/rpc-spec-types@2.3.0(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + typescript: 7.0.2 + + '@solana/rpc-spec-types@5.5.1(typescript@7.0.2)': + optionalDependencies: + typescript: 7.0.2 - '@solana/functional@2.3.0(typescript@6.0.3)': + '@solana/rpc-spec@2.3.0(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 - '@solana/instructions@2.3.0(typescript@6.0.3)': + '@solana/rpc-spec@5.5.1(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 - '@solana/keys@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/rpc-subscriptions-api@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/assertions': 2.3.0(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 2.3.0(typescript@7.0.2) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/instructions': 2.3.0(typescript@6.0.3) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/programs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-parsed-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/signers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/sysvars': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/rpc-subscriptions-api@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 5.5.1(typescript@7.0.2) + '@solana/rpc-transformers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - - ws + optional: true + + '@solana/rpc-subscriptions-channel-websocket@2.3.0(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + dependencies: + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 2.3.0(typescript@7.0.2) + '@solana/subscribable': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + + '@solana/rpc-subscriptions-channel-websocket@5.5.1(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 5.5.1(typescript@7.0.2) + '@solana/subscribable': 5.5.1(typescript@7.0.2) + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + optional: true - '@solana/nominal-types@2.3.0(typescript@6.0.3)': + '@solana/rpc-subscriptions-spec@2.3.0(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/promises': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + '@solana/subscribable': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 - '@solana/nominal-types@3.0.3(typescript@6.0.3)': + '@solana/rpc-subscriptions-spec@5.5.1(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/promises': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + '@solana/subscribable': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + optional: true - '@solana/options@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/rpc-subscriptions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-data-structures': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/fast-stable-stringify': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/promises': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-subscriptions-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions-channel-websocket': 2.3.0(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/rpc-subscriptions-spec': 2.3.0(typescript@7.0.2) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/subscribable': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + - ws - '@solana/options@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-data-structures': 3.0.3(typescript@6.0.3) - '@solana/codecs-numbers': 3.0.3(typescript@6.0.3) - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/rpc-subscriptions@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/fast-stable-stringify': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/promises': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-subscriptions-api': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions-channel-websocket': 5.5.1(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/rpc-subscriptions-spec': 5.5.1(typescript@7.0.2) + '@solana/rpc-transformers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/subscribable': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: + - bufferutil - fastestsmallesttextencoderdecoder + - utf-8-validate + optional: true - '@solana/programs@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/rpc-transformers@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/promises@2.3.0(typescript@6.0.3)': - dependencies: - typescript: 6.0.3 - - '@solana/rpc-api@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/rpc-transformers@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-parsed-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec': 2.3.0(typescript@6.0.3) - '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + optional: true - '@solana/rpc-parsed-types@2.3.0(typescript@6.0.3)': + '@solana/rpc-transport-http@2.3.0(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + undici-types: 7.28.0 - '@solana/rpc-spec-types@2.3.0(typescript@6.0.3)': + '@solana/rpc-transport-http@5.5.1(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + undici-types: 7.28.0 + optionalDependencies: + typescript: 7.0.2 + optional: true - '@solana/rpc-spec-types@3.0.3(typescript@6.0.3)': + '@solana/rpc-types@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder - '@solana/rpc-spec@2.3.0(typescript@6.0.3)': + '@solana/rpc-types@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder - '@solana/rpc-spec@3.0.3(typescript@6.0.3)': + '@solana/rpc@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 3.0.3(typescript@6.0.3) - '@solana/rpc-spec-types': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/fast-stable-stringify': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/rpc-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-spec': 2.3.0(typescript@7.0.2) + '@solana/rpc-spec-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-transport-http': 2.3.0(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder - '@solana/rpc-subscriptions-api@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-subscriptions-spec': 2.3.0(typescript@6.0.3) - '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/rpc@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/fast-stable-stringify': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/rpc-api': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-spec': 5.5.1(typescript@7.0.2) + '@solana/rpc-spec-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-transformers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-transport-http': 5.5.1(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + optional: true - '@solana/rpc-subscriptions-channel-websocket@2.3.0(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@solana/signers@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/rpc-subscriptions-spec': 2.3.0(typescript@6.0.3) - '@solana/subscribable': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 - ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - - '@solana/rpc-subscriptions-spec@2.3.0(typescript@6.0.3)': - dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/promises': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - '@solana/subscribable': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 - - '@solana/rpc-subscriptions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/fast-stable-stringify': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/promises': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-subscriptions-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-subscriptions-channel-websocket': 2.3.0(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-subscriptions-spec': 2.3.0(typescript@6.0.3) - '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/subscribable': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/instructions': 2.3.0(typescript@7.0.2) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - - ws - '@solana/rpc-transformers@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/signers@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/instructions': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + '@solana/offchain-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + optional: true - '@solana/rpc-transport-http@2.3.0(typescript@6.0.3)': + '@solana/subscribable@2.3.0(typescript@7.0.2)': dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 - undici-types: 7.25.0 + '@solana/errors': 2.3.0(typescript@7.0.2) + typescript: 7.0.2 - '@solana/rpc-types@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/subscribable@5.5.1(typescript@7.0.2)': dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder + '@solana/errors': 5.5.1(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 + optional: true - '@solana/rpc-types@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/sysvars@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-numbers': 3.0.3(typescript@6.0.3) - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - '@solana/nominal-types': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 + '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/fast-stable-stringify': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/rpc-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-spec': 2.3.0(typescript@6.0.3) - '@solana/rpc-spec-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-transport-http': 2.3.0(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/accounts': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/signers@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/transaction-confirmation@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/instructions': 2.3.0(typescript@6.0.3) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/promises': 2.3.0(typescript@7.0.2) + '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + - ws - '@solana/subscribable@2.3.0(typescript@6.0.3)': - dependencies: - '@solana/errors': 2.3.0(typescript@6.0.3) - typescript: 6.0.3 - - '@solana/sysvars@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/transaction-confirmation@5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/promises': 5.5.1(typescript@7.0.2) + '@solana/rpc': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions': 5.5.1(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: + - bufferutil - fastestsmallesttextencoderdecoder + - utf-8-validate + optional: true - '@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/transaction-messages@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/accounts': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - '@solana/rpc-types': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-data-structures': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/instructions': 2.3.0(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/transaction-confirmation@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/promises': 2.3.0(typescript@6.0.3) - '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/transaction-messages@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-data-structures': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/instructions': 5.5.1(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - - ws + optional: true - '@solana/transaction-messages@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': + '@solana/transactions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-data-structures': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/instructions': 2.3.0(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 2.3.0(typescript@7.0.2) + '@solana/codecs-data-structures': 2.3.0(typescript@7.0.2) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 2.3.0(typescript@7.0.2) + '@solana/functional': 2.3.0(typescript@7.0.2) + '@solana/instructions': 2.3.0(typescript@7.0.2) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 2.3.0(typescript@7.0.2) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/transactions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/codecs-core': 2.3.0(typescript@6.0.3) - '@solana/codecs-data-structures': 2.3.0(typescript@6.0.3) - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/errors': 2.3.0(typescript@6.0.3) - '@solana/functional': 2.3.0(typescript@6.0.3) - '@solana/instructions': 2.3.0(typescript@6.0.3) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/nominal-types': 2.3.0(typescript@6.0.3) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - typescript: 6.0.3 + '@solana/transactions@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 5.5.1(typescript@7.0.2) + '@solana/codecs-data-structures': 5.5.1(typescript@7.0.2) + '@solana/codecs-numbers': 5.5.1(typescript@7.0.2) + '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 5.5.1(typescript@7.0.2) + '@solana/functional': 5.5.1(typescript@7.0.2) + '@solana/instructions': 5.5.1(typescript@7.0.2) + '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 5.5.1(typescript@7.0.2) + '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + optionalDependencies: + typescript: 7.0.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + optional: true - '@solana/wallet-adapter-alpha@0.1.14(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-alpha@0.1.14(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-avana@0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-avana@0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: '@solana/wallet-standard-features': 1.3.0 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) '@wallet-standard/base': 1.1.0 '@wallet-standard/features': 1.1.0 eventemitter3: 5.0.4 - '@solana/wallet-adapter-bitkeep@0.3.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-bitkeep@0.3.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-bitpie@0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-bitpie@0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-clover@0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-clover@0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-coin98@0.5.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-coin98@0.5.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 6.0.0 buffer: 6.0.3 - '@solana/wallet-adapter-coinbase@0.1.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-coinbase@0.1.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-coinhub@0.3.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-coinhub@0.3.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-fractal@0.1.12(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@solana/wallet-adapter-fractal@0.1.12(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@fractalwagmi/solana-wallet-adapter': 0.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@fractalwagmi/solana-wallet-adapter': 0.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - react - react-dom - '@solana/wallet-adapter-huobi@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-huobi@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-hyperpay@0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-hyperpay@0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-keystone@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@solana/wallet-adapter-keystone@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: - '@keystonehq/sol-keyring': 0.20.0(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@keystonehq/sol-keyring': 0.20.0(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) buffer: 6.0.3 transitivePeerDependencies: - bufferutil @@ -15057,121 +16222,110 @@ snapshots: - typescript - utf-8-validate - '@solana/wallet-adapter-krystal@0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-krystal@0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-ledger@0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-ledger@0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: '@ledgerhq/devices': 8.14.2 '@ledgerhq/hw-transport': 6.35.2 '@ledgerhq/hw-transport-webhid': 6.35.2 - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) buffer: 6.0.3 - '@solana/wallet-adapter-mathwallet@0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-mathwallet@0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-neko@0.2.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-neko@0.2.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-nightly@0.1.20(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-nightly@0.1.20(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-nufi@0.1.21(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-nufi@0.1.21(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-onto@0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-onto@0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-particle@0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)': + '@solana/wallet-adapter-particle@0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)': dependencies: - '@particle-network/solana-wallet': 1.3.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@particle-network/solana-wallet': 1.3.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - bs58 - '@solana/wallet-adapter-phantom@0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': - dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - - '@solana/wallet-adapter-react@0.15.39(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)': + '@solana/wallet-adapter-phantom@0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana-mobile/wallet-adapter-mobile': 2.2.3(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - react: 19.2.7 - transitivePeerDependencies: - - bs58 - - react-native + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-safepal@0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-safepal@0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-saifu@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-saifu@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-salmon@0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-salmon@0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - salmon-adapter-sdk: 1.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + salmon-adapter-sdk: 1.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-sky@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-sky@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-solflare@0.6.33(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-solflare@0.6.33(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@solflare-wallet/sdk': 1.4.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solflare-wallet/sdk': 1.4.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-solong@0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-solong@0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-spot@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-spot@0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-tokenary@0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-tokenary@0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-tokenpocket@0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-tokenpocket@0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-torus@0.11.32(@babel/runtime@7.29.2)(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@solana/wallet-adapter-torus@0.11.32(@babel/runtime@7.29.7)(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@toruslabs/solana-embed': 2.1.0(@babel/runtime@7.29.2)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@toruslabs/solana-embed': 2.1.0(@babel/runtime@7.29.7)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) assert: 2.1.0 crypto-browserify: 3.12.1 process: 0.11.10 @@ -15185,11 +16339,11 @@ snapshots: - typescript - utf-8-validate - '@solana/wallet-adapter-trezor@0.1.6(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-trezor@0.1.6(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@trezor/connect-web': 9.7.3(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@trezor/connect-web': 9.7.3(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) buffer: 6.0.3 transitivePeerDependencies: - '@solana/sysvars' @@ -15206,24 +16360,24 @@ snapshots: - utf-8-validate - ws - '@solana/wallet-adapter-trust@0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-trust@0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-unsafe-burner@0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-unsafe-burner@0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: '@noble/curves': 1.9.7 - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) '@solana/wallet-standard-features': 1.3.0 '@solana/wallet-standard-util': 1.1.2 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-walletconnect@0.1.21(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@solana/wallet-adapter-walletconnect@0.1.21(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/solana-adapter': 0.0.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/solana-adapter': 0.0.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -15252,45 +16406,45 @@ snapshots: - utf-8-validate - zod - '@solana/wallet-adapter-wallets@0.19.38(@babel/runtime@7.29.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bs58@5.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(ioredis@5.10.1)(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76)': - dependencies: - '@solana/wallet-adapter-alpha': 0.1.14(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-avana': 0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-bitkeep': 0.3.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-bitpie': 0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-clover': 0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-coin98': 0.5.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-coinbase': 0.1.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-coinhub': 0.3.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-fractal': 0.1.12(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@solana/wallet-adapter-huobi': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-hyperpay': 0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-keystone': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-krystal': 0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-ledger': 0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-mathwallet': 0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-neko': 0.2.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-nightly': 0.1.20(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-nufi': 0.1.21(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-onto': 0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-particle': 0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0) - '@solana/wallet-adapter-phantom': 0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-safepal': 0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-saifu': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-salmon': 0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-sky': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-solflare': 0.6.33(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-solong': 0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-spot': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-tokenary': 0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-tokenpocket': 0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-torus': 0.11.32(@babel/runtime@7.29.2)(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@solana/wallet-adapter-trezor': 0.1.6(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-trust': 0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-unsafe-burner': 0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-adapter-walletconnect': 0.1.21(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@solana/wallet-adapter-xdefi': 0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-wallets@0.19.38(@babel/runtime@7.29.7)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bs58@5.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(ioredis@5.10.1)(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76)': + dependencies: + '@solana/wallet-adapter-alpha': 0.1.14(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-avana': 0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-bitkeep': 0.3.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-bitpie': 0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-clover': 0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-coin98': 0.5.24(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-coinbase': 0.1.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-coinhub': 0.3.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-fractal': 0.1.12(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@solana/wallet-adapter-huobi': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-hyperpay': 0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-keystone': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-krystal': 0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-ledger': 0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-mathwallet': 0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-neko': 0.2.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-nightly': 0.1.20(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-nufi': 0.1.21(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-onto': 0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-particle': 0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0) + '@solana/wallet-adapter-phantom': 0.9.29(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-safepal': 0.5.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-saifu': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-salmon': 0.1.18(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-sky': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-solflare': 0.6.33(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-solong': 0.9.22(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-spot': 0.1.19(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-tokenary': 0.1.16(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-tokenpocket': 0.4.23(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-torus': 0.11.32(@babel/runtime@7.29.7)(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-trezor': 0.1.6(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-trust': 0.1.17(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-unsafe-burner': 0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-walletconnect': 0.1.21(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@solana/wallet-adapter-xdefi': 0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -15332,10 +16486,10 @@ snapshots: - ws - zod - '@solana/wallet-adapter-xdefi@0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solana/wallet-adapter-xdefi@0.1.11(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) '@solana/wallet-standard-chains@1.1.1': dependencies: @@ -15358,23 +16512,23 @@ snapshots: '@solana/wallet-standard-chains': 1.1.1 '@solana/wallet-standard-features': 1.3.0 - '@solana/wallet-standard-wallet-adapter-base@1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)': + '@solana/wallet-standard-wallet-adapter-base@1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) '@solana/wallet-standard-chains': 1.1.1 '@solana/wallet-standard-features': 1.3.0 '@solana/wallet-standard-util': 1.1.2 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) '@wallet-standard/app': 1.1.0 '@wallet-standard/base': 1.1.0 '@wallet-standard/features': 1.1.0 '@wallet-standard/wallet': 1.1.0 bs58: 5.0.0 - '@solana/wallet-standard-wallet-adapter-react@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7)': + '@solana/wallet-standard-wallet-adapter-react@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7)': dependencies: - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0) '@wallet-standard/app': 1.1.0 '@wallet-standard/base': 1.1.0 react: 19.2.7 @@ -15382,33 +16536,33 @@ snapshots: - '@solana/web3.js' - bs58 - '@solana/wallet-standard-wallet-adapter@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7)': + '@solana/wallet-standard-wallet-adapter@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7)': dependencies: - '@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0) - '@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) + '@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0) + '@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) transitivePeerDependencies: - '@solana/wallet-adapter-base' - '@solana/web3.js' - bs58 - react - '@solana/wallet-standard@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7)': + '@solana/wallet-standard@1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7)': dependencies: '@solana/wallet-standard-core': 1.1.2 - '@solana/wallet-standard-wallet-adapter': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) + '@solana/wallet-standard-wallet-adapter': 1.1.4(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(bs58@5.0.0)(react@19.2.7) transitivePeerDependencies: - '@solana/wallet-adapter-base' - '@solana/web3.js' - bs58 - react - '@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.29.2 '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@solana/buffer-layout': 4.0.1 - '@solana/codecs-numbers': 2.3.0(typescript@6.0.3) + '@solana/codecs-numbers': 2.3.0(typescript@7.0.2) agentkeepalive: 4.6.0 bn.js: 5.2.3 borsh: 0.7.0 @@ -15425,9 +16579,9 @@ snapshots: - typescript - utf-8-validate - '@solflare-wallet/sdk@1.4.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))': + '@solflare-wallet/sdk@1.4.2(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))': dependencies: - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) bs58: 5.0.0 eventemitter3: 5.0.4 uuid: 9.0.1 @@ -15455,20 +16609,20 @@ snapshots: transitivePeerDependencies: - encoding - '@stakekit/rainbowkit@2.2.11(patch_hash=d028a8df4a214c19e07d959c285fb81eb54f4f1f3cbc199c7331e502cd94fbea)(@tanstack/react-query@5.100.10(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.6.14)': + '@stakekit/rainbowkit@2.2.11(patch_hash=d028a8df4a214c19e07d959c285fb81eb54f4f1f3cbc199c7331e502cd94fbea)(@tanstack/react-query@5.101.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.7.1)': dependencies: - '@tanstack/react-query': 5.100.10(react@19.2.7) + '@tanstack/react-query': 5.101.2(react@19.2.7) '@vanilla-extract/css': 1.17.3 '@vanilla-extract/dynamic': 2.1.4 '@vanilla-extract/sprinkles': 1.6.4(@vanilla-extract/css@1.17.3) clsx: 2.1.1 - cuer: 0.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + cuer: 0.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) react-remove-scroll: 2.6.2(@types/react@19.2.17)(react@19.2.7) ua-parser-js: 1.0.41 - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - wagmi: 3.6.14(33458ed1d2c332a5e309c6e4b0d54aab) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + wagmi: 3.7.1(23bf2f6635616981ad1339a134a5f675) transitivePeerDependencies: - '@types/react' - babel-plugin-macros @@ -15510,25 +16664,25 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/query-core@5.100.10': {} + '@tanstack/query-core@5.101.2': {} - '@tanstack/react-query@5.100.10(react@19.2.7)': + '@tanstack/react-query@5.101.2(react@19.2.7)': dependencies: - '@tanstack/query-core': 5.100.10 + '@tanstack/query-core': 5.101.2 react: 19.2.7 - '@tanstack/react-virtual@3.13.24(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@tanstack/react-virtual@3.14.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@tanstack/virtual-core': 3.14.0 + '@tanstack/virtual-core': 3.17.3 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@tanstack/virtual-core@3.14.0': {} + '@tanstack/virtual-core@3.17.3': {} '@testing-library/jest-dom@5.17.0': dependencies: - '@adobe/css-tools': 4.4.4 - '@babel/runtime': 7.29.2 + '@adobe/css-tools': 4.5.0 + '@babel/runtime': 7.29.7 '@types/testing-library__jest-dom': 5.14.9 aria-query: 5.3.2 chalk: 3.0.0 @@ -15588,14 +16742,14 @@ snapshots: transitivePeerDependencies: - encoding - '@toruslabs/base-controllers@5.11.0(@babel/runtime@7.29.2)(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@toruslabs/base-controllers@5.11.0(@babel/runtime@7.29.7)(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@ethereumjs/util': 9.1.0 '@toruslabs/broadcast-channel': 10.0.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@toruslabs/http-helpers': 6.1.1(@babel/runtime@7.29.2) - '@toruslabs/openlogin-jrpc': 8.3.0(@babel/runtime@7.29.2) - '@toruslabs/openlogin-utils': 8.2.1(@babel/runtime@7.29.2) + '@toruslabs/http-helpers': 6.1.1(@babel/runtime@7.29.7) + '@toruslabs/openlogin-jrpc': 8.3.0(@babel/runtime@7.29.7) + '@toruslabs/openlogin-utils': 8.2.1(@babel/runtime@7.29.7) async-mutex: 0.5.0 bignumber.js: 9.3.1 bowser: 2.14.1 @@ -15609,9 +16763,9 @@ snapshots: '@toruslabs/broadcast-channel@10.0.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@toruslabs/eccrypto': 4.0.0 - '@toruslabs/metadata-helpers': 5.1.0(@babel/runtime@7.29.2) + '@toruslabs/metadata-helpers': 5.1.0(@babel/runtime@7.29.7) loglevel: 1.9.2 oblivious-set: 1.4.0 socket.io-client: 4.8.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -15622,34 +16776,34 @@ snapshots: - supports-color - utf-8-validate - '@toruslabs/constants@13.4.0(@babel/runtime@7.29.2)': + '@toruslabs/constants@13.4.0(@babel/runtime@7.29.7)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@toruslabs/eccrypto@4.0.0': dependencies: elliptic: 6.6.1 - '@toruslabs/http-helpers@6.1.1(@babel/runtime@7.29.2)': + '@toruslabs/http-helpers@6.1.1(@babel/runtime@7.29.7)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 lodash.merge: 4.6.2 loglevel: 1.9.2 - '@toruslabs/metadata-helpers@5.1.0(@babel/runtime@7.29.2)': + '@toruslabs/metadata-helpers@5.1.0(@babel/runtime@7.29.7)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@toruslabs/eccrypto': 4.0.0 - '@toruslabs/http-helpers': 6.1.1(@babel/runtime@7.29.2) + '@toruslabs/http-helpers': 6.1.1(@babel/runtime@7.29.7) elliptic: 6.6.1 ethereum-cryptography: 2.2.1 json-stable-stringify: 1.3.0 transitivePeerDependencies: - '@sentry/types' - '@toruslabs/openlogin-jrpc@8.3.0(@babel/runtime@7.29.2)': + '@toruslabs/openlogin-jrpc@8.3.0(@babel/runtime@7.29.7)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 end-of-stream: 1.4.5 events: 3.3.0 fast-safe-stringify: 2.1.1 @@ -15657,20 +16811,20 @@ snapshots: pump: 3.0.4 readable-stream: 4.7.0 - '@toruslabs/openlogin-utils@8.2.1(@babel/runtime@7.29.2)': + '@toruslabs/openlogin-utils@8.2.1(@babel/runtime@7.29.7)': dependencies: - '@babel/runtime': 7.29.2 - '@toruslabs/constants': 13.4.0(@babel/runtime@7.29.2) + '@babel/runtime': 7.29.7 + '@toruslabs/constants': 13.4.0(@babel/runtime@7.29.7) base64url: 3.0.1 color: 4.2.3 - '@toruslabs/solana-embed@2.1.0(@babel/runtime@7.29.2)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)': + '@toruslabs/solana-embed@2.1.0(@babel/runtime@7.29.7)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)': dependencies: - '@babel/runtime': 7.29.2 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@toruslabs/base-controllers': 5.11.0(@babel/runtime@7.29.2)(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@toruslabs/http-helpers': 6.1.1(@babel/runtime@7.29.2) - '@toruslabs/openlogin-jrpc': 8.3.0(@babel/runtime@7.29.2) + '@babel/runtime': 7.29.7 + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@toruslabs/base-controllers': 5.11.0(@babel/runtime@7.29.7)(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@toruslabs/http-helpers': 6.1.1(@babel/runtime@7.29.7) + '@toruslabs/openlogin-jrpc': 8.3.0(@babel/runtime@7.29.7) eth-rpc-errors: 4.0.3 fast-deep-equal: 3.1.3 lodash-es: 4.18.1 @@ -15684,9 +16838,9 @@ snapshots: - typescript - utf-8-validate - '@trezor/analytics@1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': + '@trezor/analytics@1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': dependencies: - '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/utils': 9.5.0(tslib@2.8.1) tslib: 2.8.1 transitivePeerDependencies: @@ -15700,11 +16854,11 @@ snapshots: '@trezor/utxo-lib': 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@trezor/blockchain-link-utils@1.5.2(bufferutil@4.0.9)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(utf-8-validate@5.0.10)': + '@trezor/blockchain-link-utils@1.5.2(bufferutil@4.0.9)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(utf-8-validate@5.0.10)': dependencies: '@mobily/ts-belt': 3.13.1 '@stellar/stellar-sdk': 14.2.0 - '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/protobuf': 1.5.2(tslib@2.8.1) '@trezor/utils': 9.5.0(tslib@2.8.1) tslib: 2.8.1 @@ -15717,18 +16871,18 @@ snapshots: - react-native - utf-8-validate - '@trezor/blockchain-link@2.6.2(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@trezor/blockchain-link@2.6.2(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/stake': 0.2.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)) - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) + '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/stake': 0.2.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@stellar/stellar-sdk': 14.2.0 '@trezor/blockchain-link-types': 1.5.1(tslib@2.8.1) - '@trezor/blockchain-link-utils': 1.5.2(bufferutil@4.0.9)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(utf-8-validate@5.0.10) - '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/blockchain-link-utils': 1.5.2(bufferutil@4.0.9)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(utf-8-validate@5.0.10) + '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/utils': 9.5.0(tslib@2.8.1) '@trezor/utxo-lib': 2.5.0(tslib@2.8.1) '@trezor/websocket-client': 1.3.0(bufferutil@4.0.9)(tslib@2.8.1)(utf-8-validate@5.0.10) @@ -15751,18 +16905,18 @@ snapshots: - utf-8-validate - ws - '@trezor/connect-analytics@1.4.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': + '@trezor/connect-analytics@1.4.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': dependencies: - '@trezor/analytics': 1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/analytics': 1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) tslib: 2.8.1 transitivePeerDependencies: - expo-constants - expo-localization - react-native - '@trezor/connect-common@0.5.1(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': + '@trezor/connect-common@0.5.1(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': dependencies: - '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/type-utils': 1.2.0 '@trezor/utils': 9.5.0(tslib@2.8.1) tslib: 2.8.1 @@ -15771,10 +16925,10 @@ snapshots: - expo-localization - react-native - '@trezor/connect-web@9.7.3(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@trezor/connect-web@9.7.3(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@trezor/connect': 9.7.3(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@trezor/connect-common': 0.5.1(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/connect': 9.7.3(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@trezor/connect-common': 0.5.1(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/utils': 9.5.0(tslib@2.8.1) '@trezor/websocket-client': 1.3.0(bufferutil@4.0.9)(tslib@2.8.1)(utf-8-validate@5.0.10) tslib: 2.8.1 @@ -15792,7 +16946,7 @@ snapshots: - utf-8-validate - ws - '@trezor/connect@9.7.3(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@trezor/connect@9.7.3(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@ethereumjs/common': 10.1.1 '@ethereumjs/tx': 10.1.1 @@ -15800,20 +16954,20 @@ snapshots: '@mobily/ts-belt': 3.13.1 '@noble/hashes': 1.8.0 '@scure/bip39': 1.6.0 - '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/system': 0.7.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)) - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@trezor/blockchain-link': 2.6.2(@solana/sysvars@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/system': 0.7.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@trezor/blockchain-link': 2.6.2(@solana/sysvars@5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@trezor/blockchain-link-types': 1.5.1(tslib@2.8.1) - '@trezor/blockchain-link-utils': 1.5.2(bufferutil@4.0.9)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(utf-8-validate@5.0.10) - '@trezor/connect-analytics': 1.4.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) - '@trezor/connect-common': 0.5.1(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/blockchain-link-utils': 1.5.2(bufferutil@4.0.9)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)(utf-8-validate@5.0.10) + '@trezor/connect-analytics': 1.4.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/connect-common': 0.5.1(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/crypto-utils': 1.2.0(tslib@2.8.1) '@trezor/device-authenticity': 1.1.2(tslib@2.8.1) '@trezor/device-utils': 1.2.0 - '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) + '@trezor/env-utils': 1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1) '@trezor/protobuf': 1.5.3(tslib@2.8.1) '@trezor/protocol': 1.3.1(tslib@2.8.1) '@trezor/schema-utils': 1.4.0(tslib@2.8.1) @@ -15858,12 +17012,12 @@ snapshots: '@trezor/device-utils@1.2.0': {} - '@trezor/env-utils@1.5.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': + '@trezor/env-utils@1.5.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(tslib@2.8.1)': dependencies: tslib: 2.8.1 ua-parser-js: 2.0.9 optionalDependencies: - react-native: 0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) + react-native: 0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) '@trezor/protobuf@1.5.2(tslib@2.8.1)': dependencies: @@ -15933,12 +17087,12 @@ snapshots: dependencies: '@trezor/utils': 9.5.0(tslib@2.8.1) tslib: 2.8.1 - ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - '@tronweb3/tronwallet-abstract-adapter@1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@tronweb3/tronwallet-abstract-adapter@1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: eventemitter3: 4.0.7 tronweb: 6.2.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -15947,45 +17101,46 @@ snapshots: - debug - utf-8-validate - '@tronweb3/tronwallet-adapter-bitkeep@1.1.8(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@tronweb3/tronwallet-adapter-bitkeep@1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: - '@tronweb3/tronwallet-abstract-adapter': 1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@tronweb3/tronwallet-adapter-tronlink': 1.1.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@tronweb3/tronwallet-abstract-adapter': 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@tronweb3/tronwallet-adapter-tronlink': 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - debug - utf-8-validate - '@tronweb3/tronwallet-adapter-ledger@1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@tronweb3/tronwallet-adapter-ledger@1.1.14(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@ledgerhq/hw-app-trx': 6.29.2 '@ledgerhq/hw-transport': 6.27.1 '@ledgerhq/hw-transport-webhid': 6.27.1 '@testing-library/jest-dom': 5.17.0 - '@tronweb3/tronwallet-abstract-adapter': 1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@tronweb3/tronwallet-abstract-adapter': 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) buffer: 6.0.3 eventemitter3: 5.0.4 preact: 10.23.0 - tronweb: 6.2.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) + tronweb: 6.4.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - debug + - supports-color - utf-8-validate - '@tronweb3/tronwallet-adapter-tronlink@1.1.16(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@tronweb3/tronwallet-adapter-tronlink@1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: - '@tronweb3/tronwallet-abstract-adapter': 1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@tronweb3/tronwallet-abstract-adapter': 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - debug - utf-8-validate - '@tronweb3/tronwallet-adapter-walletconnect@3.0.3(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@tronweb3/tronwallet-adapter-walletconnect@3.0.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@tronweb3/tronwallet-abstract-adapter': 1.1.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@tronweb3/walletconnect-tron': 4.0.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@tronweb3/tronwallet-abstract-adapter': 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@tronweb3/walletconnect-tron': 4.0.3(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/modal': 2.6.2(@types/react@19.2.17)(react@19.2.7) - '@walletconnect/types': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -16008,19 +17163,22 @@ snapshots: - db0 - debug - encoding + - fastestsmallesttextencoderdecoder + - immer - ioredis - react - typescript - uploadthing + - use-sync-external-store - utf-8-validate - zod - '@tronweb3/walletconnect-tron@4.0.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@tronweb3/walletconnect-tron@4.0.3(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit': 1.7.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/sign-client': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/universal-provider': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/utils': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit': 1.8.20(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@11.1.11)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -16041,33 +17199,37 @@ snapshots: - aws4fetch - bufferutil - db0 + - debug - encoding + - fastestsmallesttextencoderdecoder + - immer - ioredis - react - typescript - uploadthing + - use-sync-external-store - utf-8-validate - zod - '@turbo/darwin-64@2.9.18': + '@turbo/darwin-64@2.10.4': optional: true - '@turbo/darwin-arm64@2.9.18': + '@turbo/darwin-arm64@2.10.4': optional: true - '@turbo/linux-64@2.9.18': + '@turbo/linux-64@2.10.4': optional: true - '@turbo/linux-arm64@2.9.18': + '@turbo/linux-arm64@2.10.4': optional: true - '@turbo/windows-64@2.9.18': + '@turbo/windows-64@2.10.4': optional: true - '@turbo/windows-arm64@2.9.18': + '@turbo/windows-arm64@2.10.4': optional: true - '@tybys/wasm-util@0.10.2': + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true @@ -16095,11 +17257,11 @@ snapshots: '@types/base32-encoding@1.0.2': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.0 '@types/bn.js@5.2.0': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.0 '@types/chai@5.2.3': dependencies: @@ -16108,31 +17270,31 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.0 '@types/css-font-loading-module@0.0.7': {} - '@types/d3-array@3.0.3': {} + '@types/d3-array@3.2.2': {} - '@types/d3-color@3.1.0': {} + '@types/d3-color@3.1.3': {} '@types/d3-ease@3.0.2': {} - '@types/d3-interpolate@3.0.1': + '@types/d3-interpolate@3.0.4': dependencies: - '@types/d3-color': 3.1.0 + '@types/d3-color': 3.1.3 '@types/d3-path@3.1.1': {} - '@types/d3-scale@4.0.2': + '@types/d3-scale@4.0.9': dependencies: - '@types/d3-time': 3.0.0 + '@types/d3-time': 3.0.4 '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 - '@types/d3-time@3.0.0': {} + '@types/d3-time@3.0.4': {} '@types/d3-timer@3.0.2': {} @@ -16145,7 +17307,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -16166,8 +17328,6 @@ snapshots: '@types/json-logic-js@2.0.5': {} - '@types/json-schema@7.0.15': {} - '@types/lodash.merge@4.6.9': dependencies: '@types/lodash': 4.17.16 @@ -16192,11 +17352,11 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@25.7.0': + '@types/node@26.1.0': dependencies: - undici-types: 7.21.0 + undici-types: 8.3.0 - '@types/node@26.0.0': + '@types/node@26.1.1': dependencies: undici-types: 8.3.0 @@ -16210,7 +17370,7 @@ snapshots: '@types/set-cookie-parser@2.4.10': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.1 '@types/stack-utils@2.0.3': {} @@ -16232,11 +17392,11 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.0 '@types/ws@8.18.1': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.1 '@types/yargs-parser@21.0.3': {} @@ -16244,18 +17404,82 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@typescript/typescript6@6.0.2': + dependencies: + '@typescript/old': typescript@6.0.3 + '@vanilla-extract/babel-plugin-debug-ids@1.2.2': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 transitivePeerDependencies: - supports-color - '@vanilla-extract/compiler@0.7.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)': + '@vanilla-extract/compiler@0.7.1(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)': dependencies: - '@vanilla-extract/css': 1.20.1 + '@vanilla-extract/css': 1.21.1 '@vanilla-extract/integration': 8.0.10 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) - vite-node: 6.0.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) + vite-node: 6.0.0(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -16289,7 +17513,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - '@vanilla-extract/css@1.20.1': + '@vanilla-extract/css@1.21.1': dependencies: '@emotion/hash': 0.9.2 '@vanilla-extract/private': 1.0.9 @@ -16315,12 +17539,12 @@ snapshots: '@vanilla-extract/integration@8.0.10': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) '@vanilla-extract/babel-plugin-debug-ids': 1.2.2 - '@vanilla-extract/css': 1.20.1 + '@vanilla-extract/css': 1.21.1 dedent: 1.7.2 - esbuild: 0.28.0 + esbuild: 0.28.1 eval: 0.1.8 find-up: 5.0.0 javascript-stringify: 2.1.0 @@ -16331,23 +17555,23 @@ snapshots: '@vanilla-extract/private@1.0.9': {} - '@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.20.1)': + '@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.21.1)': dependencies: - '@vanilla-extract/css': 1.20.1 + '@vanilla-extract/css': 1.21.1 '@vanilla-extract/sprinkles@1.6.4(@vanilla-extract/css@1.17.3)': dependencies: '@vanilla-extract/css': 1.17.3 - '@vanilla-extract/sprinkles@1.6.5(@vanilla-extract/css@1.20.1)': + '@vanilla-extract/sprinkles@1.7.0(@vanilla-extract/css@1.21.1)': dependencies: - '@vanilla-extract/css': 1.20.1 + '@vanilla-extract/css': 1.21.1 - '@vanilla-extract/vite-plugin@5.2.2(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(yaml@2.9.0)': + '@vanilla-extract/vite-plugin@5.2.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0)': dependencies: - '@vanilla-extract/compiler': 0.7.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + '@vanilla-extract/compiler': 0.7.1(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) '@vanilla-extract/integration': 8.0.10 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -16364,37 +17588,37 @@ snapshots: - tsx - yaml - '@vitejs/plugin-react@6.0.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.3(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) optionalDependencies: - '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) babel-plugin-react-compiler: 1.0.0 - '@vitest/browser-playwright@4.1.9(bufferutil@4.0.9)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(playwright@1.61.0)(utf-8-validate@5.0.10)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)': + '@vitest/browser-playwright@4.1.10(bufferutil@4.0.9)(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(playwright@1.61.1)(utf-8-validate@5.0.10)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: - '@vitest/browser': 4.1.9(bufferutil@4.0.9)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9) - '@vitest/mocker': 4.1.9(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - playwright: 1.61.0 + '@vitest/browser': 4.1.10(bufferutil@4.0.9)(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(utf-8-validate@5.0.10)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) + playwright: 1.61.1 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@26.0.0)(@vitest/browser-playwright@4.1.9)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-playwright@4.1.10)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.9(bufferutil@4.0.9)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)': + '@vitest/browser@4.1.10(bufferutil@4.0.9)(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(utf-8-validate@5.0.10)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/utils': 4.1.9 + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@26.0.0)(@vitest/browser-playwright@4.1.9)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-playwright@4.1.10)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -16402,69 +17626,69 @@ snapshots: - utf-8-validate - vite - '@vitest/expect@4.1.9': + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.9 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.14.6(@types/node@26.0.0)(typescript@6.0.3) - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + msw: 2.15.0(@types/node@26.1.1)(typescript@7.0.2) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) - '@vitest/pretty-format@4.1.9': + '@vitest/pretty-format@4.1.10': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.9': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 4.1.9 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - '@vitest/snapshot@4.1.9': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.9': {} + '@vitest/spy@4.1.10': {} - '@vitest/utils@4.1.9': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.9 + '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@wagmi/connectors@8.0.13(abec6eed1e25c11127302d84f2389259)': + '@wagmi/connectors@8.0.22(daddac613d0afa74a978e493d0be8db1)': dependencies: - '@wagmi/core': 3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@wagmi/core': 3.6.1(@tanstack/query-core@5.101.2)(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) optionalDependencies: - '@coinbase/wallet-sdk': 4.3.6(@types/react@19.2.17)(bufferutil@4.0.9)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) - '@safe-global/safe-apps-provider': 0.18.6(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/ethereum-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - porto: 0.2.35(@tanstack/react-query@5.100.10(react@19.2.7))(@types/react@19.2.17)(@wagmi/core@3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)))(immer@11.1.8)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.6.14) - typescript: 6.0.3 + '@coinbase/wallet-sdk': 4.3.6(@types/react@19.2.17)(bufferutil@4.0.9)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(utf-8-validate@5.0.10)(zod@3.25.76) + '@safe-global/safe-apps-provider': 0.18.6(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/ethereum-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + porto: 0.2.35(@tanstack/react-query@5.101.2(react@19.2.7))(@types/react@19.2.17)(@wagmi/core@3.6.1(@tanstack/query-core@5.101.2)(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)))(immer@11.1.11)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.7.1) + typescript: 7.0.2 - '@wagmi/core@3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))': + '@wagmi/core@3.6.1(@tanstack/query-core@5.101.2)(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))': dependencies: eventemitter3: 5.0.1 - mipd: 0.0.7(typescript@6.0.3) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - zustand: 5.0.0(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) + mipd: 0.0.7(typescript@7.0.2) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + zustand: 5.0.0(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) optionalDependencies: - '@tanstack/query-core': 5.100.10 - typescript: 6.0.3 + '@tanstack/query-core': 5.101.2 + typescript: 7.0.2 transitivePeerDependencies: - '@types/react' - immer @@ -16498,21 +17722,21 @@ snapshots: dependencies: '@wallet-standard/base': 1.1.0 - '@walletconnect/core@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 events: 3.3.0 lodash.isequal: 4.5.0 @@ -16542,21 +17766,21 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.33.0 events: 3.3.0 @@ -16586,21 +17810,21 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.33.0 events: 3.3.0 @@ -16631,21 +17855,21 @@ snapshots: - zod optional: true - '@walletconnect/core@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.33.0 events: 3.3.0 @@ -16676,21 +17900,21 @@ snapshots: - zod optional: true - '@walletconnect/core@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.39.3 events: 3.3.0 @@ -16720,23 +17944,23 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/logger': 2.1.2 + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/logger': 3.0.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 - es-toolkit: 1.39.3 + es-toolkit: 1.44.0 events: 3.3.0 uint8arrays: 3.1.1 transitivePeerDependencies: @@ -16764,21 +17988,21 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 3.0.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@6.0.3)(zod@3.25.76) + '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.44.0 events: 3.3.0 @@ -16812,18 +18036,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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/ethereum-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/universal-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/universal-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -16907,18 +18131,18 @@ snapshots: '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/safe-json': 1.0.2 events: 3.3.0 - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - '@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/safe-json': 1.0.2 - idb-keyval: 6.2.2 - unstorage: 1.17.5(idb-keyval@6.2.2)(ioredis@5.10.1) + idb-keyval: 6.2.6 + unstorage: 1.17.5(idb-keyval@6.2.6)(ioredis@5.10.1) optionalDependencies: - '@react-native-async-storage/async-storage': 1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)) + '@react-native-async-storage/async-storage': 1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -16995,16 +18219,16 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/sign-client@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@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.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17031,16 +18255,16 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@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.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17067,16 +18291,16 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17104,16 +18328,16 @@ snapshots: - zod optional: true - '@walletconnect/sign-client@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17141,16 +18365,16 @@ snapshots: - zod optional: true - '@walletconnect/sign-client@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@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.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17177,16 +18401,16 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/logger': 2.1.2 + '@walletconnect/logger': 3.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17213,16 +18437,16 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/logger': 3.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@6.0.3)(zod@3.25.76) + '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17249,13 +18473,13 @@ snapshots: - utf-8-validate - zod - '@walletconnect/solana-adapter@0.0.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/solana-adapter@0.0.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@solana/wallet-adapter-base@0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)))(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@reown/appkit': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@reown/appkit': 1.7.2(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@types/react@19.2.17)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(react@19.2.7)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@solana/wallet-adapter-base': 0.9.27(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17289,12 +18513,12 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.1 '@walletconnect/jsonrpc-types': 1.0.3 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.3 events: 3.3.0 transitivePeerDependencies: @@ -17318,12 +18542,12 @@ snapshots: - ioredis - uploadthing - '@walletconnect/types@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -17347,12 +18571,12 @@ snapshots: - ioredis - uploadthing - '@walletconnect/types@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -17376,12 +18600,12 @@ snapshots: - ioredis - uploadthing - '@walletconnect/types@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -17406,12 +18630,12 @@ snapshots: - uploadthing optional: true - '@walletconnect/types@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -17436,12 +18660,12 @@ snapshots: - uploadthing optional: true - '@walletconnect/types@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -17465,13 +18689,13 @@ snapshots: - ioredis - uploadthing - '@walletconnect/types@2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/logger': 2.1.2 + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/logger': 3.0.2 events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17494,12 +18718,12 @@ snapshots: - ioredis - uploadthing - '@walletconnect/types@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': + '@walletconnect/types@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 3.0.2 events: 3.3.0 transitivePeerDependencies: @@ -17523,18 +18747,18 @@ snapshots: - ioredis - uploadthing - '@walletconnect/universal-provider@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/universal-provider@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 lodash: 4.17.21 transitivePeerDependencies: @@ -17563,18 +18787,18 @@ snapshots: - utf-8-validate - zod - '@walletconnect/universal-provider@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/universal-provider@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) es-toolkit: 1.33.0 events: 3.3.0 transitivePeerDependencies: @@ -17603,18 +18827,18 @@ snapshots: - utf-8-validate - zod - '@walletconnect/universal-provider@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/universal-provider@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) es-toolkit: 1.33.0 events: 3.3.0 transitivePeerDependencies: @@ -17644,18 +18868,18 @@ snapshots: - zod optional: true - '@walletconnect/universal-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/universal-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) es-toolkit: 1.33.0 events: 3.3.0 transitivePeerDependencies: @@ -17685,18 +18909,18 @@ snapshots: - zod optional: true - '@walletconnect/universal-provider@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/universal-provider@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) es-toolkit: 1.39.3 events: 3.3.0 transitivePeerDependencies: @@ -17725,19 +18949,59 @@ snapshots: - utf-8-validate - zod - '@walletconnect/universal-provider@2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/universal-provider@2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/types': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) - '@walletconnect/utils': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) - es-toolkit: 1.39.3 + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/logger': 3.0.2 + '@walletconnect/sign-client': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) + es-toolkit: 1.44.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - encoding + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + + '@walletconnect/universal-provider@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) + '@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.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/logger': 3.0.2 + '@walletconnect/sign-client': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/utils': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76) + es-toolkit: 1.44.0 events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -17765,25 +19029,25 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@noble/ciphers': 1.2.1 '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.19.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 detect-browser: 5.3.0 elliptic: 6.6.1 query-string: 7.1.3 uint8arrays: 3.1.0 - viem: 2.23.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.23.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -17809,18 +19073,18 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@noble/ciphers': 1.2.1 '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.19.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 bs58: 6.0.0 @@ -17828,7 +19092,7 @@ snapshots: elliptic: 6.6.1 query-string: 7.1.3 uint8arrays: 3.1.0 - viem: 2.23.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.23.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -17854,25 +19118,25 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@noble/ciphers': 1.2.1 '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 bs58: 6.0.0 detect-browser: 5.3.0 query-string: 7.1.3 uint8arrays: 3.1.0 - viem: 2.23.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.23.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -17899,25 +19163,25 @@ snapshots: - zod optional: true - '@walletconnect/utils@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@noble/ciphers': 1.2.1 '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@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.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 bs58: 6.0.0 detect-browser: 5.3.0 query-string: 7.1.3 uint8arrays: 3.1.0 - viem: 2.23.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.23.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -17944,7 +19208,7 @@ snapshots: - zod optional: true - '@walletconnect/utils@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@msgpack/msgpack': 3.1.2 '@noble/ciphers': 1.3.0 @@ -17952,12 +19216,12 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.21.4(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 blakejs: 1.2.1 @@ -17965,7 +19229,7 @@ snapshots: detect-browser: 5.3.0 query-string: 7.1.3 uint8arrays: 3.1.1 - viem: 2.31.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.31.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -17991,28 +19255,27 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76)': dependencies: - '@msgpack/msgpack': 3.1.2 + '@msgpack/msgpack': 3.1.3 '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.2 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/logger': 3.0.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.23.7(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 blakejs: 1.2.1 - bs58: 6.0.0 detect-browser: 5.3.0 - query-string: 7.1.3 + ox: 0.9.3(typescript@7.0.2)(zod@3.25.76) uint8arrays: 3.1.1 - viem: 2.31.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -18030,15 +19293,13 @@ snapshots: - '@vercel/functions' - '@vercel/kv' - aws4fetch - - bufferutil - db0 - ioredis - typescript - uploadthing - - utf-8-validate - zod - '@walletconnect/utils@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@6.0.3)(zod@3.25.76)': + '@walletconnect/utils@2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)(typescript@7.0.2)(zod@3.25.76)': dependencies: '@msgpack/msgpack': 3.1.3 '@noble/ciphers': 1.3.0 @@ -18046,18 +19307,18 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/logger': 3.0.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) + '@walletconnect/types': 2.23.9(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 blakejs: 1.2.1 detect-browser: 5.3.0 - ox: 0.9.3(typescript@6.0.3)(zod@3.25.76) + ox: 0.9.3(typescript@7.0.2)(zod@3.25.76) uint8arrays: 3.1.1 transitivePeerDependencies: - '@azure/app-configuration' @@ -18110,20 +19371,6 @@ snapshots: '@xstate/fsm@1.6.5': {} - '@xstate/react@6.1.0(@types/react@19.2.17)(react@19.2.7)(xstate@5.31.1)': - dependencies: - react: 19.2.7 - use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.17)(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - optionalDependencies: - xstate: 5.31.1 - transitivePeerDependencies: - - '@types/react' - - '@xstate/store@3.17.5(react@19.2.7)': - optionalDependencies: - react: 19.2.7 - '@zeit/schemas@2.36.0': {} '@zondax/ledger-js@1.3.1': @@ -18148,34 +19395,40 @@ snapshots: fs-extra: 10.1.0 yargs: 17.7.3 - abitype@1.0.8(typescript@6.0.3)(zod@3.25.76): + abitype@1.0.6(typescript@7.0.2)(zod@3.25.76): + optionalDependencies: + typescript: 7.0.2 + zod: 3.25.76 + optional: true + + abitype@1.0.8(typescript@7.0.2)(zod@3.25.76): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 zod: 3.25.76 - abitype@1.2.3(typescript@6.0.3)(zod@3.22.4): + abitype@1.2.3(typescript@7.0.2)(zod@3.22.4): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 zod: 3.22.4 - abitype@1.2.3(typescript@6.0.3)(zod@3.25.76): + abitype@1.2.3(typescript@7.0.2)(zod@3.25.76): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 zod: 3.25.76 - abitype@1.2.4(typescript@6.0.3)(zod@3.22.4): + abitype@1.2.4(typescript@7.0.2)(zod@3.22.4): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 zod: 3.22.4 - abitype@1.2.4(typescript@6.0.3)(zod@3.25.76): + abitype@1.2.4(typescript@7.0.2)(zod@3.25.76): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 zod: 3.25.76 - abitype@1.2.4(typescript@6.0.3)(zod@4.4.3): + abitype@1.2.4(typescript@7.0.2)(zod@4.4.3): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 zod: 4.4.3 optional: true @@ -18183,19 +19436,23 @@ snapshots: dependencies: event-target-shim: 5.0.1 - abort-error@1.0.1: {} + abort-error@1.0.2: {} accepts@2.0.0: dependencies: mime-types: 3.0.2 negotiator: 1.0.0 - acorn@8.16.0: {} - acorn@8.17.0: {} aes-js@4.0.0-beta.5: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + agent-base@7.1.4: {} agentkeepalive@4.6.0: @@ -18212,7 +19469,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 3.1.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -18251,14 +19508,14 @@ snapshots: argparse@2.0.1: {} + argue-cli@3.1.0: {} + aria-hidden@1.2.6: dependencies: tslib: 2.8.1 aria-query@5.3.2: {} - array-ify@1.0.0: {} - asap@2.0.6: {} asn1.js@4.10.1: @@ -18293,19 +19550,25 @@ snapshots: atomic-sleep@1.0.0: {} - autoprefixer@10.5.0(postcss@8.5.14): + autoprefixer@10.5.2(postcss@8.5.16): dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001792 + browserslist: 4.28.4 + caniuse-lite: 1.0.30001800 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.14 + postcss: 8.5.16 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 + axios-retry@4.5.0(axios@1.16.0): + dependencies: + axios: 1.16.0 + is-retry-allowed: 2.2.0 + optional: true + axios@1.13.5: dependencies: follow-redirects: 1.16.0 @@ -18322,15 +19585,25 @@ snapshots: transitivePeerDependencies: - debug - b4a@1.7.3: {} + axios@1.18.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color - babel-jest@29.7.0(@babel/core@7.29.0): + b4a@1.8.1: {} + + babel-jest@29.7.0(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.0) + babel-preset-jest: 29.6.3(@babel/core@7.29.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -18356,36 +19629,36 @@ snapshots: babel-plugin-react-compiler@1.0.0: dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 babel-plugin-syntax-hermes-parser@0.29.1: dependencies: hermes-parser: 0.29.1 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - - babel-preset-jest@29.6.3(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-jest@29.6.3(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) balanced-match@1.0.2: {} @@ -18407,9 +19680,7 @@ snapshots: base64url@3.0.1: {} - baseline-browser-mapping@2.10.29: {} - - baseline-browser-mapping@2.10.38: {} + baseline-browser-mapping@2.10.41: {} bech32@1.1.4: {} @@ -18420,7 +19691,6 @@ snapshots: bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 - optional: true big-integer@1.6.36: {} @@ -18428,7 +19698,7 @@ snapshots: big.js@6.2.2: {} - bignumber.js@11.1.1: {} + bignumber.js@11.1.5: {} bignumber.js@9.1.2: {} @@ -18464,7 +19734,7 @@ snapshots: blake2b-wasm@2.4.0: dependencies: - b4a: 1.7.3 + b4a: 1.8.1 nanoassert: 2.0.0 transitivePeerDependencies: - react-native-b4a @@ -18480,8 +19750,12 @@ snapshots: bn.js@4.12.3: {} + bn.js@4.12.4: {} + bn.js@5.2.3: {} + bn.js@5.2.4: {} + borsh@0.7.0: dependencies: bn.js: 5.2.3 @@ -18503,7 +19777,7 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 - brace-expansion@1.1.14: + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -18562,13 +19836,13 @@ snapshots: dependencies: pako: 1.0.11 - browserslist@4.28.2: + browserslist@4.28.4: dependencies: - baseline-browser-mapping: 2.10.29 - caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.353 - node-releases: 2.0.44 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + baseline-browser-mapping: 2.10.41 + caniuse-lite: 1.0.30001800 + electron-to-chromium: 1.5.387 + node-releases: 2.0.50 + update-browserslist-db: 1.2.3(browserslist@4.28.4) bs58@4.0.1: dependencies: @@ -18626,7 +19900,7 @@ snapshots: c32check@2.0.0: dependencies: - '@noble/hashes': 1.1.5 + '@noble/hashes': 1.8.0 base-x: 4.0.1 cac@7.0.0: {} @@ -18658,9 +19932,7 @@ snapshots: camelcase@7.0.1: {} - caniuse-lite@1.0.30001792: {} - - caniuse-lite@1.0.30001799: {} + caniuse-lite@1.0.30001800: {} cardinal@2.1.1: dependencies: @@ -18681,7 +19953,7 @@ snapshots: dependencies: bindings: 1.5.0 inherits: 2.0.4 - nan: 2.23.1 + nan: 2.28.0 optional: true chacha@2.1.0: @@ -18715,6 +19987,9 @@ snapshots: chalk@5.6.2: {} + charenc@0.0.2: + optional: true + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -18726,7 +20001,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -18735,7 +20010,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -18827,12 +20102,9 @@ snapshots: commander@14.0.0: {} - commander@2.20.3: {} + commander@14.0.2: {} - compare-func@2.0.0: - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 + commander@2.20.3: {} compressible@2.0.18: dependencies: @@ -18869,18 +20141,18 @@ snapshots: content-disposition@0.5.2: {} - conventional-changelog-angular@8.3.1: + conventional-changelog-angular@9.2.1: dependencies: - compare-func: 2.0.0 + '@conventional-changelog/template': 1.2.1 - conventional-changelog-conventionalcommits@9.3.1: + conventional-changelog-conventionalcommits@10.2.0: dependencies: - compare-func: 2.0.0 + '@conventional-changelog/template': 1.2.0 - conventional-commits-parser@6.4.0: + conventional-commits-parser@7.1.0: dependencies: - '@simple-libs/stream-utils': 1.2.0 - meow: 13.2.0 + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.1.0 convert-source-map@2.0.0: {} @@ -18895,21 +20167,21 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.3.0(@types/node@25.7.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): dependencies: - '@types/node': 25.7.0 - cosmiconfig: 9.0.2(typescript@6.0.3) + '@types/node': 26.1.1 + cosmiconfig: 9.0.2(typescript@7.0.2) jiti: 2.6.1 - typescript: 6.0.3 + typescript: 7.0.2 - cosmiconfig@9.0.2(typescript@6.0.3): + cosmiconfig@9.0.2(typescript@7.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.2.0 + js-yaml: 4.3.0 parse-json: 5.2.0 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 cosmjs-types@0.10.1: {} @@ -18965,6 +20237,9 @@ snapshots: dependencies: uncrypto: 0.1.3 + crypt@0.0.2: + optional: true + crypto-browserify@3.12.0: dependencies: browserify-cipher: 1.0.1 @@ -19000,7 +20275,6 @@ snapshots: dependencies: mdn-data: 2.27.1 source-map-js: 1.2.1 - optional: true css-what@6.2.2: {} @@ -19011,22 +20285,21 @@ snapshots: cssstyle@5.3.7: dependencies: '@asamuzakjp/css-color': 4.1.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) css-tree: 3.2.1 lru-cache: 11.5.1 - optional: true csstype@3.2.3: {} - cuer@0.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3): + cuer@0.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2): dependencies: qr: 0.5.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 - d3-array@3.2.1: + d3-array@3.2.4: dependencies: internmap: 2.0.3 @@ -19034,7 +20307,7 @@ snapshots: d3-ease@3.0.1: {} - d3-format@3.1.0: {} + d3-format@3.1.2: {} d3-interpolate@3.0.1: dependencies: @@ -19044,8 +20317,8 @@ snapshots: d3-scale@4.0.2: dependencies: - d3-array: 3.2.1 - d3-format: 3.1.0 + d3-array: 3.2.4 + d3-format: 3.1.2 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 @@ -19060,7 +20333,7 @@ snapshots: d3-time@3.1.0: dependencies: - d3-array: 3.2.1 + d3-array: 3.2.4 d3-timer@3.0.1: {} @@ -19068,9 +20341,6 @@ snapshots: dependencies: whatwg-mimetype: 5.0.0 whatwg-url: 15.1.0 - optional: true - - date-fns@4.1.0: {} dayjs@1.11.13: {} @@ -19086,8 +20356,7 @@ snapshots: decimal.js-light@2.5.1: {} - decimal.js@10.6.0: - optional: true + decimal.js@10.6.0: {} decode-uri-component@0.2.2: {} @@ -19172,20 +20441,12 @@ snapshots: dijkstrajs@1.0.3: {} - dns-packet@5.6.1: - dependencies: - '@leichtgewicht/ip-codec': 2.0.5 - dom-accessibility-api@0.5.16: {} dom-walk@0.1.2: {} domain-browser@4.22.0: {} - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 - draggabilly@3.0.0: dependencies: get-size: 3.0.0 @@ -19212,20 +20473,20 @@ snapshots: ee-first@1.1.1: {} - effect@4.0.0-beta.68: + effect@4.0.0-beta.97: dependencies: '@standard-schema/spec': 1.1.0 - fast-check: 4.8.0 + fast-check: 4.9.0 find-my-way-ts: 0.1.6 ini: 7.0.0 kubernetes-types: 1.30.0 - msgpackr: 2.0.1 - multipasta: 0.2.7 - toml: 4.1.1 - uuid: 14.0.0 + msgpackr: 2.0.4 + multipasta: 0.2.8 + toml: 4.1.2 + uuid: 14.0.1 yaml: 2.9.0 - electron-to-chromium@1.5.353: {} + electron-to-chromium@1.5.387: {} elliptic@6.6.1: dependencies: @@ -19272,8 +20533,7 @@ snapshots: engine.io-parser@5.2.3: {} - entities@8.0.0: - optional: true + entities@8.0.0: {} env-paths@2.2.1: {} @@ -19296,6 +20556,8 @@ snapshots: es-module-lexer@2.1.0: {} + es-module-lexer@2.3.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -19313,9 +20575,7 @@ snapshots: es-toolkit@1.44.0: {} - es-toolkit@1.46.1: {} - - es-toolkit@1.48.1: {} + es-toolkit@1.49.0: {} es6-promise@3.3.1: {} @@ -19325,35 +20585,6 @@ snapshots: dependencies: es6-promise: 4.2.8 - esbuild@0.28.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 - esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -19429,7 +20660,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.0 require-like: 0.1.2 event-target-shim@5.0.1: {} @@ -19463,7 +20694,7 @@ snapshots: exenv@1.2.2: {} - expect-type@1.3.0: {} + expect-type@1.4.0: {} expect@30.4.1: dependencies: @@ -19482,7 +20713,7 @@ snapshots: eyes@0.1.8: {} - fast-check@4.8.0: + fast-check@4.9.0: dependencies: pure-rand: 8.4.0 @@ -19504,7 +20735,9 @@ snapshots: fast-uri@3.1.2: {} - fast-wrap-ansi@0.2.0: + fast-uri@3.1.3: {} + + fast-wrap-ansi@0.2.2: dependencies: fast-string-width: 3.0.2 @@ -19518,9 +20751,13 @@ snapshots: dependencies: bser: 2.1.1 - fdir@6.5.0(picomatch@4.0.4): + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 feaxios@0.0.23: dependencies: @@ -19579,14 +20816,26 @@ snapshots: hasown: 2.0.3 mime-types: 2.1.35 + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + fraction.js@4.0.1: {} fraction.js@5.3.4: {} - framer-motion@12.38.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + framer-motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - motion-dom: 12.38.0 - motion-utils: 12.36.0 + motion-dom: 12.42.2 + motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: react: 19.2.7 @@ -19646,13 +20895,9 @@ snapshots: get-stream@6.0.1: {} - git-raw-commits@5.0.1(conventional-commits-parser@6.4.0): + get-tsconfig@4.14.0: dependencies: - '@conventional-changelog/git-client': 2.7.0(conventional-commits-parser@6.4.0) - meow: 13.2.0 - transitivePeerDependencies: - - conventional-commits-filter - - conventional-commits-parser + resolve-pkg-maps: 1.0.0 glob@7.2.3: dependencies: @@ -19683,7 +20928,7 @@ snapshots: graceful-fs@4.2.11: {} - graphql@16.14.0: {} + graphql@16.14.2: {} h3@1.15.11: dependencies: @@ -19743,10 +20988,14 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + headers-polyfill@5.0.1: dependencies: '@types/set-cookie-parser': 2.4.10 - set-cookie-parser: 3.1.0 + set-cookie-parser: 3.1.2 hermes-estree@0.29.1: {} @@ -19768,13 +21017,12 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hono@4.12.26: + hono@4.12.27: optional: true html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 - optional: true html-parse-stringify@3.0.1: dependencies: @@ -19794,12 +21042,18 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color - optional: true http2-client@1.3.5: {} https-browserify@1.0.0: {} + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -19819,24 +21073,20 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 - i18next@26.1.0(typescript@6.0.3): + i18next@26.3.6(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 i@0.3.7: {} iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - optional: true idb-keyval@6.2.1: optional: true - idb-keyval@6.2.2: {} - - idb-keyval@6.2.5: - optional: true + idb-keyval@6.2.6: {} ieee754@1.2.1: {} @@ -19847,11 +21097,9 @@ snapshots: dependencies: queue: 6.0.2 - immer@10.2.0: {} - - immer@11.1.8: {} + immer@11.1.11: {} - immutable@5.1.6: + immutable@5.1.9: optional: true import-fresh@3.3.1: @@ -19916,6 +21164,9 @@ snapshots: is-arrayish@0.3.4: {} + is-buffer@1.1.6: + optional: true + is-callable@1.2.7: {} is-core-module@2.16.2: @@ -19952,8 +21203,6 @@ snapshots: is-number@7.0.0: {} - is-obj@2.0.0: {} - is-plain-obj@2.1.0: optional: true @@ -19961,8 +21210,7 @@ snapshots: is-port-reachable@4.0.0: {} - is-potential-custom-element-name@1.0.1: - optional: true + is-potential-custom-element-name@1.0.1: {} is-regex@1.2.1: dependencies: @@ -19971,6 +21219,9 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.3 + is-retry-allowed@2.2.0: + optional: true + is-retry-allowed@3.0.0: {} is-standalone-pwa@0.1.1: {} @@ -20011,9 +21262,9 @@ snapshots: isomorphic-timers-promises@1.0.1: {} - isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + isomorphic-ws@4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) isows@1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: @@ -20023,20 +21274,15 @@ snapshots: dependencies: ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) - isows@1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + isows@1.0.7(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: - ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - - isows@1.0.7(ws@8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)): - dependencies: - ws: 8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) - optional: true + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 @@ -20055,11 +21301,11 @@ snapshots: delay: 5.0.0 es6-promisify: 5.0.0 eyes: 0.1.8 - isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + isomorphic-ws: 4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)) json-stringify-safe: 5.0.1 stream-json: 1.9.1 uuid: 8.3.2 - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -20076,7 +21322,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 26.0.0 + '@types/node': 26.1.1 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -20086,7 +21332,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 26.0.0 + '@types/node': 26.1.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -20119,13 +21365,13 @@ snapshots: jest-message-util@30.4.1: dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@jest/types': 30.4.1 '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 jest-util: 30.4.1 - picomatch: 4.0.4 + picomatch: 4.0.5 pretty-format: 30.4.1 slash: 3.0.0 stack-utils: 2.0.6 @@ -20133,13 +21379,13 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 26.0.0 + '@types/node': 26.1.1 jest-util: 29.7.0 jest-mock@30.4.1: dependencies: '@jest/types': 30.4.1 - '@types/node': 25.7.0 + '@types/node': 26.1.1 jest-util: 30.4.1 jest-regex-util@29.6.3: {} @@ -20149,7 +21395,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 26.0.0 + '@types/node': 26.1.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -20158,11 +21404,11 @@ snapshots: jest-util@30.4.1: dependencies: '@jest/types': 30.4.1 - '@types/node': 25.7.0 + '@types/node': 26.1.1 chalk: 4.1.2 ci-info: 4.4.0 graceful-fs: 4.2.11 - picomatch: 4.0.4 + picomatch: 4.0.5 jest-validate@29.7.0: dependencies: @@ -20175,14 +21421,16 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 jiti@2.6.1: {} - jiti@2.7.0: + jiti@2.7.0: {} + + jose@6.2.3: optional: true js-base64@3.7.8: {} @@ -20191,12 +21439,12 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.14.2: + js-yaml@3.15.0: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.2.0: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 @@ -20220,7 +21468,7 @@ snapshots: parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.1 + tough-cookie: 6.0.2 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-encoding: 3.1.1 @@ -20232,7 +21480,6 @@ snapshots: - bufferutil - supports-color - utf-8-validate - optional: true jsesc@3.1.0: {} @@ -20296,6 +21543,22 @@ snapshots: keyvaluestorage-interface@1.0.0: {} + knip@6.26.0: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + formatly: 0.3.0 + get-tsconfig: 4.14.0 + jiti: 2.7.0 + oxc-parser: 0.137.0 + oxc-resolver: 11.21.3 + picomatch: 4.0.5 + smol-toml: 1.7.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.2 + yaml: 2.9.0 + zod: 4.4.3 + kubernetes-types@1.30.0: {} less@4.2.2: @@ -20315,18 +21578,22 @@ snapshots: leven@3.1.0: {} - libsodium-sumo@0.7.15: {} - libsodium-sumo@0.7.16: {} - libsodium-wrappers-sumo@0.7.15: + libsodium-sumo@0.8.4: {} + + libsodium-wrappers-sumo@0.7.10: dependencies: - libsodium-sumo: 0.7.15 + libsodium-sumo: 0.7.16 libsodium-wrappers-sumo@0.7.16: dependencies: libsodium-sumo: 0.7.16 + libsodium-wrappers-sumo@0.8.4: + dependencies: + libsodium-sumo: 0.8.4 + lighthouse-logger@1.4.2: dependencies: debug: 2.6.9 @@ -20387,21 +21654,21 @@ snapshots: lit-element@3.3.3: dependencies: - '@lit-labs/ssr-dom-shim': 1.5.1 + '@lit-labs/ssr-dom-shim': 1.6.0 '@lit/reactive-element': 1.6.3 lit-html: 2.8.0 lit-element@4.2.2: dependencies: - '@lit-labs/ssr-dom-shim': 1.5.1 + '@lit-labs/ssr-dom-shim': 1.6.0 '@lit/reactive-element': 2.1.2 - lit-html: 3.3.2 + lit-html: 3.3.3 lit-html@2.8.0: dependencies: '@types/trusted-types': 2.0.7 - lit-html@3.3.2: + lit-html@3.3.3: dependencies: '@types/trusted-types': 2.0.7 @@ -20415,13 +21682,19 @@ snapshots: dependencies: '@lit/reactive-element': 2.1.2 lit-element: 4.2.2 - lit-html: 3.3.2 + lit-html: 3.3.3 lit@3.3.0: dependencies: '@lit/reactive-element': 2.1.2 lit-element: 4.2.2 - lit-html: 3.3.2 + lit-html: 3.3.3 + + lit@3.3.3: + dependencies: + '@lit/reactive-element': 2.1.2 + lit-element: 4.2.2 + lit-html: 3.3.3 locate-path@5.0.0: dependencies: @@ -20472,10 +21745,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.6: {} - - lru-cache@11.5.1: - optional: true + lru-cache@11.5.1: {} lru-cache@5.1.1: dependencies: @@ -20489,6 +21759,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + main-event@1.0.4: {} + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -20509,17 +21781,21 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - mdn-data@2.27.1: + md5@2.3.0: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 optional: true + mdn-data@2.27.1: {} + media-query-parser@2.0.2: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 memoize-one@5.2.1: {} - meow@13.2.0: {} - merge-options@3.0.4: dependencies: is-plain-obj: 2.1.0 @@ -20529,7 +21805,7 @@ snapshots: metro-babel-transformer@0.83.7: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 flow-enums-runtime: 0.0.6 hermes-parser: 0.35.0 metro-cache-key: 0.83.7 @@ -20596,7 +21872,7 @@ snapshots: metro-runtime@0.83.7: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 flow-enums-runtime: 0.0.6 metro-source-map@0.83.7: @@ -20626,7 +21902,7 @@ snapshots: metro-transform-plugins@0.83.7: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/template': 7.29.7 '@babel/traverse': 7.29.7 @@ -20637,7 +21913,7 @@ snapshots: metro-transform-worker@0.83.7(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 @@ -20658,7 +21934,7 @@ snapshots: metro@0.83.7(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 @@ -20749,17 +22025,17 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.14 + brace-expansion: 1.1.15 minimist@1.2.8: {} - mipd@0.0.7(typescript@6.0.3): + mipd@0.0.7(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 mitt@3.0.1: {} - mixpanel-browser@2.78.0: + mixpanel-browser@2.81.0: dependencies: '@mixpanel/rrweb': 2.0.0-alpha.18.4 '@mixpanel/rrweb-plugin-console-record': 2.0.0-alpha.18.4(@mixpanel/rrweb-utils@2.0.0-alpha.18.4)(@mixpanel/rrweb@2.0.0-alpha.18.4) @@ -20771,7 +22047,7 @@ snapshots: mlly@1.8.2: dependencies: - acorn: 8.16.0 + acorn: 8.17.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.4 @@ -20780,11 +22056,11 @@ snapshots: modern-ahocorasick@1.1.0: {} - motion-dom@12.38.0: + motion-dom@12.42.2: dependencies: - motion-utils: 12.36.0 + motion-utils: 12.39.0 - motion-utils@12.36.0: {} + motion-utils@12.39.0: {} motion@10.16.2: dependencies: @@ -20795,9 +22071,9 @@ snapshots: '@motionone/utils': 10.18.0 '@motionone/vue': 10.16.4 - motion@12.38.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - framer-motion: 12.38.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + framer-motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tslib: 2.8.1 optionalDependencies: react: 19.2.7 @@ -20809,30 +22085,30 @@ snapshots: ms@2.1.3: {} - msgpackr-extract@3.0.3: + msgpackr-extract@3.0.4: dependencies: node-gyp-build-optional-packages: 5.2.2 optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 optional: true - msgpackr@2.0.1: + msgpackr@2.0.4: optionalDependencies: - msgpackr-extract: 3.0.3 + msgpackr-extract: 3.0.4 - msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3): + msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2): dependencies: - '@inquirer/confirm': 6.0.13(@types/node@26.0.0) + '@inquirer/confirm': 6.1.1(@types/node@26.1.1) '@mswjs/interceptors': 0.41.9 '@open-draft/deferred-promise': 3.0.0 '@types/statuses': 2.0.6 cookie: 1.1.1 - graphql: 16.14.0 + graphql: 16.14.2 headers-polyfill: 5.0.1 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -20841,30 +22117,33 @@ snapshots: rettime: 0.11.11 statuses: 2.0.2 strict-event-emitter: 0.5.1 - tough-cookie: 6.0.1 - type-fest: 5.6.0 + tough-cookie: 6.0.2 + type-fest: 5.8.0 until-async: 3.0.2 - yargs: 17.7.2 + yargs: 17.7.3 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - '@types/node' - multiformats@13.4.1: {} + multiformats@13.4.2: {} + + multiformats@14.0.4: {} multiformats@9.9.0: {} - multipasta@0.2.7: {} + multipasta@0.2.8: {} mute-stream@3.0.0: {} nan@2.23.1: {} - nanoassert@2.0.0: {} + nan@2.28.0: + optional: true - nanoid@3.3.12: {} + nanoassert@2.0.0: {} - nanoid@3.3.14: {} + nanoid@3.3.15: {} needle@3.5.0: dependencies: @@ -20878,25 +22157,25 @@ snapshots: neo-async@2.6.2: {} - next@16.2.9(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.85.0): + next@16.2.10(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.85.0): dependencies: - '@next/env': 16.2.9 + '@next/env': 16.2.10 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.38 - caniuse-lite: 1.0.30001799 + baseline-browser-mapping: 2.10.41 + caniuse-lite: 1.0.30001800 postcss: 8.4.31 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) styled-jsx: 5.1.6(react@19.2.7) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.9 - '@next/swc-darwin-x64': 16.2.9 - '@next/swc-linux-arm64-gnu': 16.2.9 - '@next/swc-linux-arm64-musl': 16.2.9 - '@next/swc-linux-x64-gnu': 16.2.9 - '@next/swc-linux-x64-musl': 16.2.9 - '@next/swc-win32-arm64-msvc': 16.2.9 - '@next/swc-win32-x64-msvc': 16.2.9 + '@next/swc-darwin-arm64': 16.2.10 + '@next/swc-darwin-x64': 16.2.10 + '@next/swc-linux-arm64-gnu': 16.2.10 + '@next/swc-linux-arm64-musl': 16.2.10 + '@next/swc-linux-x64-gnu': 16.2.10 + '@next/swc-linux-x64-musl': 16.2.10 + '@next/swc-win32-arm64-msvc': 16.2.10 + '@next/swc-win32-x64-msvc': 16.2.10 babel-plugin-react-compiler: 1.0.0 sass: 1.85.0 sharp: 0.34.5 @@ -20946,7 +22225,7 @@ snapshots: dependencies: es6-promise: 3.3.1 - node-releases@2.0.44: {} + node-releases@2.0.50: {} node-stdlib-browser@1.3.1: dependencies: @@ -20986,8 +22265,6 @@ snapshots: dependencies: path-key: 3.1.1 - npm@9.9.4: {} - nullthrows@1.1.1: {} oas-kit-common@1.0.8: @@ -21006,7 +22283,7 @@ snapshots: oas-kit-common: 1.0.8 reftools: 1.1.9 yaml: 1.10.3 - yargs: 17.7.2 + yargs: 17.7.3 oas-schema-walker@1.1.5: {} @@ -21102,37 +22379,7 @@ snapshots: outvariant@1.4.3: {} - ox@0.14.20(typescript@6.0.3)(zod@3.22.4): - 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.4(typescript@6.0.3)(zod@3.22.4) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - zod - - ox@0.14.20(typescript@6.0.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.4(typescript@6.0.3)(zod@3.25.76) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - zod - - ox@0.14.29(typescript@6.0.3)(zod@3.22.4): + ox@0.14.30(typescript@7.0.2)(zod@3.22.4): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -21140,15 +22387,14 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@6.0.3)(zod@3.22.4) + abitype: 1.2.4(typescript@7.0.2)(zod@3.22.4) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod - optional: true - ox@0.14.29(typescript@6.0.3)(zod@3.25.76): + ox@0.14.30(typescript@7.0.2)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -21156,44 +22402,43 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@6.0.3)(zod@3.25.76) + abitype: 1.2.4(typescript@7.0.2)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod - optional: true - ox@0.6.7(typescript@6.0.3)(zod@3.25.76): + ox@0.6.7(typescript@7.0.2)(zod@3.25.76): 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.5.4 - abitype: 1.2.4(typescript@6.0.3)(zod@3.25.76) + '@scure/bip39': 1.6.0 + abitype: 1.2.4(typescript@7.0.2)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod - ox@0.6.9(typescript@6.0.3)(zod@3.25.76): + ox@0.6.9(typescript@7.0.2)(zod@3.25.76): 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.4(typescript@6.0.3)(zod@3.25.76) + abitype: 1.2.4(typescript@7.0.2)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod optional: true - ox@0.7.1(typescript@6.0.3)(zod@3.25.76): + ox@0.7.1(typescript@7.0.2)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -21201,14 +22446,14 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.4(typescript@6.0.3)(zod@3.25.76) + abitype: 1.2.4(typescript@7.0.2)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod - ox@0.9.17(typescript@6.0.3)(zod@4.4.3): + ox@0.9.17(typescript@7.0.2)(zod@4.4.3): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -21216,15 +22461,15 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.4(typescript@6.0.3)(zod@4.4.3) + abitype: 1.2.4(typescript@7.0.2)(zod@4.4.3) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod optional: true - ox@0.9.3(typescript@6.0.3)(zod@3.25.76): + ox@0.9.3(typescript@7.0.2)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -21232,13 +22477,60 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.4(typescript@6.0.3)(zod@3.25.76) + abitype: 1.2.4(typescript@7.0.2)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - zod + oxc-parser@0.137.0: + dependencies: + '@oxc-project/types': 0.137.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.137.0 + '@oxc-parser/binding-android-arm64': 0.137.0 + '@oxc-parser/binding-darwin-arm64': 0.137.0 + '@oxc-parser/binding-darwin-x64': 0.137.0 + '@oxc-parser/binding-freebsd-x64': 0.137.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.137.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.137.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.137.0 + '@oxc-parser/binding-linux-arm64-musl': 0.137.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.137.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-musl': 0.137.0 + '@oxc-parser/binding-openharmony-arm64': 0.137.0 + '@oxc-parser/binding-wasm32-wasi': 0.137.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.137.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.137.0 + '@oxc-parser/binding-win32-x64-msvc': 0.137.0 + + oxc-resolver@11.21.3: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.21.3 + '@oxc-resolver/binding-android-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-x64': 11.21.3 + '@oxc-resolver/binding-freebsd-x64': 11.21.3 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.3 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.3 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-musl': 11.21.3 + '@oxc-resolver/binding-openharmony-arm64': 11.21.3 + '@oxc-resolver/binding-wasm32-wasi': 11.21.3 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.3 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.3 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -21255,7 +22547,7 @@ snapshots: dependencies: p-limit: 3.1.0 - p-queue@9.0.0: + p-queue@9.3.1: dependencies: eventemitter3: 5.0.4 p-timeout: 7.0.1 @@ -21268,6 +22560,8 @@ snapshots: pako@2.1.0: {} + pako@2.2.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -21293,7 +22587,6 @@ snapshots: parse5@8.0.1: dependencies: entities: 8.0.0 - optional: true parseurl@1.3.3: {} @@ -21324,11 +22617,20 @@ snapshots: sha.js: 2.4.12 to-buffer: 1.2.2 + pbkdf2@3.1.6: + dependencies: + create-hash: 1.2.0 + create-hmac: 1.1.7 + ripemd160: 2.0.3 + safe-buffer: 5.2.1 + sha.js: 2.4.12 + to-buffer: 1.2.2 + picocolors@1.1.1: {} picomatch@2.3.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} pify@4.0.1: optional: true @@ -21358,7 +22660,7 @@ snapshots: safe-stable-stringify: 2.5.0 slow-redact: 0.3.2 sonic-boom: 4.2.1 - thread-stream: 3.1.0 + thread-stream: 3.2.0 pino@7.11.0: dependencies: @@ -21386,11 +22688,11 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 - playwright-core@1.61.0: {} + playwright-core@1.61.1: {} - playwright@1.61.0: + playwright@1.61.1: dependencies: - playwright-core: 1.61.0 + playwright-core: 1.61.1 optionalDependencies: fsevents: 2.3.2 @@ -21398,22 +22700,22 @@ snapshots: pngjs@7.0.0: {} - porto@0.2.35(@tanstack/react-query@5.100.10(react@19.2.7))(@types/react@19.2.17)(@wagmi/core@3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)))(immer@11.1.8)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.6.14): + porto@0.2.35(@tanstack/react-query@5.101.2(react@19.2.7))(@types/react@19.2.17)(@wagmi/core@3.6.1(@tanstack/query-core@5.101.2)(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)))(immer@11.1.11)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.7.1): dependencies: - '@wagmi/core': 3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)) - hono: 4.12.26 - idb-keyval: 6.2.5 - mipd: 0.0.7(typescript@6.0.3) - ox: 0.9.17(typescript@6.0.3)(zod@4.4.3) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@wagmi/core': 3.6.1(@tanstack/query-core@5.101.2)(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)) + hono: 4.12.27 + idb-keyval: 6.2.6 + mipd: 0.0.7(typescript@7.0.2) + ox: 0.9.17(typescript@7.0.2)(zod@4.4.3) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) zod: 4.4.3 - zustand: 5.0.14(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) + zustand: 5.0.14(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)) optionalDependencies: - '@tanstack/react-query': 5.100.10(react@19.2.7) + '@tanstack/react-query': 5.101.2(react@19.2.7) react: 19.2.7 - react-native: 0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) - typescript: 6.0.3 - wagmi: 3.6.14(33458ed1d2c332a5e309c6e4b0d54aab) + react-native: 0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) + typescript: 7.0.2 + wagmi: 3.7.1(23bf2f6635616981ad1339a134a5f675) transitivePeerDependencies: - '@types/react' - immer @@ -21426,19 +22728,13 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.14 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.14: - dependencies: - nanoid: 3.3.12 + nanoid: 3.3.15 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.15: + postcss@8.5.16: dependencies: - nanoid: 3.3.14 + nanoid: 3.3.15 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -21447,7 +22743,7 @@ snapshots: preact@10.24.2: optional: true - prettier@3.8.4: {} + prettier@3.9.4: {} pretty-format@29.7.0: dependencies: @@ -21470,7 +22766,7 @@ snapshots: process@0.11.10: {} - progress-events@1.0.1: {} + progress-events@1.1.0: {} promise@8.3.0: dependencies: @@ -21497,7 +22793,7 @@ snapshots: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.1 '@types/long': 4.0.2 - '@types/node': 25.7.0 + '@types/node': 26.1.0 long: 4.0.0 protobufjs@7.4.0: @@ -21512,7 +22808,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.1 - '@types/node': 25.7.0 + '@types/node': 26.1.1 long: 5.2.5 protobufjs@7.5.5: @@ -21527,7 +22823,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.1 - '@types/node': 25.7.0 + '@types/node': 26.1.1 long: 5.2.5 proxy-compare@2.5.1: {} @@ -21567,10 +22863,6 @@ snapshots: pure-rand@8.4.0: {} - purify-ts@2.1.4(patch_hash=5a25e93220157c14f071521854d75c4da38ec0e15fad1989f51e87be19c2fd63): - dependencies: - '@types/json-schema': 7.0.15 - pushdata-bitcoin@1.0.1: dependencies: bitcoin-ops: 1.4.1 @@ -21644,7 +22936,7 @@ snapshots: react-devtools-core@6.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: - shell-quote: 1.8.4 + shell-quote: 1.9.0 ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -21655,17 +22947,17 @@ snapshots: react: 19.2.7 scheduler: 0.27.0 - react-i18next@17.0.7(i18next@26.1.0(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@6.0.3): + react-i18next@17.0.9(i18next@26.3.6(typescript@7.0.2))(react-dom@19.2.7(react@19.2.7))(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@7.0.2): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 html-parse-stringify: 3.0.1 - i18next: 26.1.0(typescript@6.0.3) + i18next: 26.3.6(typescript@7.0.2) react: 19.2.7 use-sync-external-store: 1.6.0(react@19.2.7) optionalDependencies: react-dom: 19.2.7(react@19.2.7) - react-native: 0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) - typescript: 6.0.3 + react-native: 0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10) + typescript: 7.0.2 react-is@16.13.1: {} @@ -21688,20 +22980,20 @@ snapshots: react-lifecycles-compat: 3.0.4 warning: 4.0.3 - react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10): + react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.81.1 - '@react-native/codegen': 0.81.1(@babel/core@7.29.0) + '@react-native/codegen': 0.81.1(@babel/core@7.29.7) '@react-native/community-cli-plugin': 0.81.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@react-native/gradle-plugin': 0.81.1 '@react-native/js-polyfills': 0.81.1 '@react-native/normalize-colors': 0.81.1 - '@react-native/virtualized-lists': 0.81.1(@types/react@19.2.17)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) + '@react-native/virtualized-lists': 0.81.1(@types/react@19.2.17)(react-native@0.81.1(@babel/core@7.29.7)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 29.7.0(@babel/core@7.29.7) babel-plugin-syntax-hermes-parser: 0.29.1 base64-js: 1.5.1 commander: 12.1.0 @@ -21773,7 +23065,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - react-remove-scroll@2.7.1(@types/react@19.2.17)(react@19.2.7): + react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7): dependencies: react: 19.2.7 react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7) @@ -21844,19 +23136,19 @@ snapshots: real-require@0.2.0: {} - recharts@3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1): + recharts@3.9.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.46.1 + es-toolkit: 1.49.0 eventemitter3: 5.0.4 - immer: 10.2.0 + immer: 11.1.11 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) react-is: 19.2.7 react-redux: 9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) - reselect: 5.1.1 + reselect: 5.2.0 tiny-invariant: 1.3.3 use-sync-external-store: 1.6.0(react@19.2.7) victory-vendor: 37.3.6 @@ -21910,12 +23202,14 @@ snapshots: requires-port@1.0.0: {} - reselect@5.1.1: {} + reselect@5.2.0: {} resolve-from@4.0.0: {} resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -21932,11 +23226,11 @@ snapshots: reusify@1.1.0: {} - rev-dep@2.15.0: + rev-dep@2.18.0: optionalDependencies: - '@rev-dep/darwin-arm64': 2.15.0 - '@rev-dep/linux-x64': 2.15.0 - '@rev-dep/win32-x64': 2.15.0 + '@rev-dep/darwin-arm64': 2.18.0 + '@rev-dep/linux-x64': 2.18.0 + '@rev-dep/win32-x64': 2.18.0 rimraf@3.0.2: dependencies: @@ -21973,32 +23267,32 @@ snapshots: - bufferutil - utf-8-validate - rolldown-string@0.3.0(rolldown@1.0.3): + rolldown-string@0.3.0(rolldown@1.1.5): dependencies: magic-string: 0.30.21 optionalDependencies: - rolldown: 1.0.3 + rolldown: 1.1.5 - rolldown@1.0.3: + rolldown@1.1.5: dependencies: - '@oxc-project/types': 0.133.0 + '@oxc-project/types': 0.139.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 rollup@4.53.3: dependencies: @@ -22037,7 +23331,7 @@ snapshots: buffer: 6.0.3 eventemitter3: 5.0.4 uuid: 8.3.2 - ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: bufferutil: 4.0.9 utf-8-validate: 5.0.10 @@ -22066,19 +23360,18 @@ snapshots: safe-stable-stringify@2.5.0: {} - safer-buffer@2.1.2: - optional: true + safer-buffer@2.1.2: {} - salmon-adapter-sdk@1.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)): + salmon-adapter-sdk@1.1.1(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)): dependencies: - '@project-serum/sol-wallet-adapter': 0.2.6(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10)) - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@6.0.3)(utf-8-validate@5.0.10) + '@project-serum/sol-wallet-adapter': 0.2.6(@solana/web3.js@1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@7.0.2)(utf-8-validate@5.0.10) eventemitter3: 4.0.7 sass@1.85.0: dependencies: chokidar: 4.0.3 - immutable: 5.1.6 + immutable: 5.1.9 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.6 @@ -22090,7 +23383,6 @@ snapshots: saxes@6.0.0: dependencies: xmlchars: 2.2.0 - optional: true scale-ts@1.6.1: {} @@ -22178,7 +23470,7 @@ snapshots: set-cookie-parser@2.7.2: {} - set-cookie-parser@3.1.0: {} + set-cookie-parser@3.1.2: {} set-function-length@1.2.2: dependencies: @@ -22237,7 +23529,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.4: {} + shell-quote@1.9.0: {} should-equal@2.0.0: dependencies: @@ -22315,6 +23607,8 @@ snapshots: smart-buffer@4.2.0: {} + smol-toml@1.7.0: {} + smoldot@2.0.40(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -22404,7 +23698,7 @@ snapshots: fetch-cookie: 3.0.1 isomorphic-fetch: 3.0.0(encoding@0.1.13) lossless-json: 4.3.0 - pako: 2.1.0 + pako: 2.2.0 starknet-types-07: '@starknet-io/types-js@0.7.10' ts-mixer: 6.0.4 transitivePeerDependencies: @@ -22486,6 +23780,8 @@ snapshots: strip-json-comments@2.0.1: {} + strip-json-comments@5.0.3: {} + styled-jsx@5.1.6(react@19.2.7): dependencies: client-only: 0.0.1 @@ -22521,8 +23817,7 @@ snapshots: symbol-observable@2.0.3: {} - symbol-tree@3.2.4: - optional: true + symbol-tree@3.2.4: {} tagged-tag@1.0.0: {} @@ -22545,7 +23840,7 @@ snapshots: dependencies: real-require: 0.1.0 - thread-stream@3.1.0: + thread-stream@3.2.0: dependencies: real-require: 0.2.0 @@ -22560,7 +23855,7 @@ snapshots: tiny-secp256k1@1.1.7: dependencies: bindings: 1.5.0 - bn.js: 4.12.3 + bn.js: 4.12.4 create-hmac: 1.1.7 elliptic: 6.6.1 nan: 2.23.1 @@ -22571,16 +23866,16 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 tinyrainbow@3.1.0: {} - tldts-core@7.0.30: {} + tldts-core@7.4.8: {} - tldts@7.0.30: + tldts@7.4.8: dependencies: - tldts-core: 7.0.30 + tldts-core: 7.4.8 tmpl@1.0.5: {} @@ -22598,7 +23893,7 @@ snapshots: toml@3.0.0: {} - toml@4.1.1: {} + toml@4.1.2: {} totalist@3.0.1: {} @@ -22609,16 +23904,15 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 - tough-cookie@6.0.1: + tough-cookie@6.0.2: dependencies: - tldts: 7.0.30 + tldts: 7.4.8 tr46@0.0.3: {} tr46@6.0.0: dependencies: punycode: 2.3.1 - optional: true tronweb@6.2.2(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: @@ -22636,6 +23930,23 @@ snapshots: - debug - utf-8-validate + tronweb@6.4.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + '@babel/runtime': 7.26.10 + axios: 1.18.0 + bignumber.js: 9.1.2 + ethereum-cryptography: 2.2.1 + ethers: 6.13.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) + eventemitter3: 5.0.1 + google-protobuf: 3.21.4 + semver: 7.7.1 + validator: 13.15.23 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + ts-custom-error@3.3.1: {} ts-log@2.2.7: {} @@ -22648,7 +23959,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.22.4: + tsx@4.23.0: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -22656,14 +23967,14 @@ snapshots: tty-browserify@0.0.1: {} - turbo@2.9.18: + turbo@2.10.4: optionalDependencies: - '@turbo/darwin-64': 2.9.18 - '@turbo/darwin-arm64': 2.9.18 - '@turbo/linux-64': 2.9.18 - '@turbo/linux-arm64': 2.9.18 - '@turbo/windows-64': 2.9.18 - '@turbo/windows-arm64': 2.9.18 + '@turbo/darwin-64': 2.10.4 + '@turbo/darwin-arm64': 2.10.4 + '@turbo/linux-64': 2.10.4 + '@turbo/linux-arm64': 2.10.4 + '@turbo/windows-64': 2.10.4 + '@turbo/windows-arm64': 2.10.4 tweetnacl-util@0.15.1: {} @@ -22677,7 +23988,7 @@ snapshots: type-fest@2.19.0: {} - type-fest@5.6.0: + type-fest@5.8.0: dependencies: tagged-tag: 1.0.0 @@ -22691,6 +24002,29 @@ snapshots: typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + ua-is-frozen@0.1.2: {} ua-parser-js@1.0.41: {} @@ -22706,16 +24040,25 @@ snapshots: uglify-js@3.19.3: optional: true - uint8-varint@2.0.4: + uint8-varint@2.0.5: + dependencies: + uint8arraylist: 2.4.9 + uint8arrays: 5.1.1 + + uint8-varint@3.0.0: dependencies: - uint8arraylist: 2.4.8 - uint8arrays: 5.1.0 + uint8arraylist: 3.0.2 + uint8arrays: 6.1.1 uint8array-tools@0.0.8: {} - uint8arraylist@2.4.8: + uint8arraylist@2.4.9: + dependencies: + uint8arrays: 5.1.1 + + uint8arraylist@3.0.2: dependencies: - uint8arrays: 5.1.0 + uint8arrays: 6.1.1 uint8arrays@3.1.0: dependencies: @@ -22725,9 +24068,15 @@ snapshots: dependencies: multiformats: 9.9.0 - uint8arrays@5.1.0: + uint8arrays@5.1.1: + dependencies: + multiformats: 13.4.2 + + uint8arrays@6.1.1: dependencies: - multiformats: 13.4.1 + multiformats: 14.0.4 + + unbash@4.0.2: {} uncrypto@0.1.3: {} @@ -22735,13 +24084,11 @@ snapshots: undici-types@6.19.8: {} - undici-types@7.21.0: {} - - undici-types@7.25.0: {} + undici-types@7.28.0: {} undici-types@8.3.0: {} - undici@8.3.0: {} + undici@8.7.0: {} unidragger@3.0.1: dependencies: @@ -22755,15 +24102,15 @@ snapshots: unpipe@1.0.0: {} - unplugin-macros@0.20.2(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(rolldown@1.0.3)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0): + unplugin-macros@0.20.2(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(rolldown@1.1.5)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0): dependencies: '@babel/types': 8.0.0 ast-kit: 3.0.0 magic-string-ast: 2.0.0 - rolldown-string: 0.3.0(rolldown@1.0.3) + rolldown-string: 0.3.0(rolldown@1.1.5) unplugin: 3.0.0 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) - vite-node: 6.0.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) + vite-node: 6.0.0(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -22782,28 +24129,28 @@ snapshots: unplugin@3.0.0: dependencies: '@jridgewell/remapping': 2.3.5 - picomatch: 4.0.4 + picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 - unstorage@1.17.5(idb-keyval@6.2.2)(ioredis@5.10.1): + unstorage@1.17.5(idb-keyval@6.2.6)(ioredis@5.10.1): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 destr: 2.0.5 h3: 1.15.11 - lru-cache: 11.3.6 + lru-cache: 11.5.1 node-fetch-native: 1.6.7 ofetch: 1.5.1 ufo: 1.6.4 optionalDependencies: - idb-keyval: 6.2.2 + idb-keyval: 6.2.6 ioredis: 5.10.1 until-async@3.0.2: {} - update-browserslist-db@1.2.3(browserslist@4.28.2): + update-browserslist-db@1.2.3(browserslist@4.28.4): dependencies: - browserslist: 4.28.2 + browserslist: 4.28.4 escalade: 3.2.0 picocolors: 1.1.1 @@ -22837,12 +24184,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - use-isomorphic-layout-effect@1.2.1(@types/react@19.2.17)(react@19.2.7): - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7): dependencies: detect-node-es: 1.1.0 @@ -22868,6 +24209,8 @@ snapshots: node-gyp-build: 4.8.4 optional: true + utf8-codec@1.0.0: {} + util-deprecate@1.0.2: {} util@0.12.5: @@ -22884,7 +24227,7 @@ snapshots: uuid@11.1.1: {} - uuid@14.0.0: {} + uuid@14.0.1: {} uuid@8.3.2: {} @@ -22914,7 +24257,7 @@ snapshots: '@types/react': 19.2.17 react: 19.2.7 - valtio@2.1.5(@types/react@19.2.17)(react@19.2.7): + valtio@2.1.7(@types/react@19.2.17)(react@19.2.7): dependencies: proxy-compare: 3.0.1 optionalDependencies: @@ -22929,14 +24272,14 @@ snapshots: victory-vendor@37.3.6: dependencies: - '@types/d3-array': 3.0.3 + '@types/d3-array': 3.2.2 '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.1 - '@types/d3-scale': 4.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.0 + '@types/d3-time': 3.0.4 '@types/d3-timer': 3.0.2 - d3-array: 3.2.1 + d3-array: 3.2.4 d3-ease: 3.0.1 d3-interpolate: 3.0.1 d3-scale: 4.0.2 @@ -22944,117 +24287,81 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - viem@2.23.2(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76): + viem@2.23.2(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76): 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@6.0.3)(zod@3.25.76) + abitype: 1.0.8(typescript@7.0.2)(zod@3.25.76) isows: 1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.6.7(typescript@6.0.3)(zod@3.25.76) + ox: 0.6.7(typescript@7.0.2)(zod@3.25.76) ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.31.0(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76): + viem@2.31.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(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.0.8(typescript@6.0.3)(zod@3.25.76) + abitype: 1.0.8(typescript@7.0.2)(zod@3.25.76) isows: 1.0.7(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.7.1(typescript@6.0.3)(zod@3.25.76) + ox: 0.7.1(typescript@7.0.2)(zod@3.25.76) ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(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@6.0.3)(zod@3.22.4) - isows: 1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.14.20(typescript@6.0.3)(zod@3.22.4) - ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(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@6.0.3)(zod@3.25.76) - isows: 1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.14.20(typescript@6.0.3)(zod@3.25.76) - ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.22.4): + viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(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@6.0.3)(zod@3.22.4) - isows: 1.0.7(ws@8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.14.29(typescript@6.0.3)(zod@3.22.4) - ws: 8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + abitype: 1.2.3(typescript@7.0.2)(zod@3.22.4) + isows: 1.0.7(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + ox: 0.14.30(typescript@7.0.2)(zod@3.22.4) + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - optional: true - viem@2.53.1(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76): + viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(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@6.0.3)(zod@3.25.76) - isows: 1.0.7(ws@8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.14.29(typescript@6.0.3)(zod@3.25.76) - ws: 8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + abitype: 1.2.3(typescript@7.0.2)(zod@3.25.76) + isows: 1.0.7(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + ox: 0.14.30(typescript@7.0.2)(zod@3.25.76) + ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - optional: true - vite-node@6.0.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0): + vite-node@6.0.0(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0): dependencies: cac: 7.0.0 es-module-lexer: 2.1.0 obug: 2.1.3 pathe: 2.0.3 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -23069,66 +24376,66 @@ snapshots: - tsx - yaml - vite-plugin-node-polyfills@0.28.0(rollup@4.53.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)): + vite-plugin-node-polyfills@0.28.0(rollup@4.53.3)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.53.3) node-stdlib-browser: 1.3.1 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - rollup - vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0): + vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.3 + picomatch: 4.0.5 + postcss: 8.5.16 + rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 less: 4.2.2 sass: 1.85.0 terser: 5.48.0 - tsx: 4.22.4 + tsx: 4.23.0 yaml: 2.9.0 - vitest-browser-react@2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9): + vitest-browser-react@2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.10): dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.9(@types/node@26.0.0)(@vitest/browser-playwright@4.1.9)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-playwright@4.1.10)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - vitest@4.1.9(@types/node@26.0.0)(@vitest/browser-playwright@4.1.9)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.9 - '@vitest/runner': 4.1.9 - '@vitest/snapshot': 4.1.9 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 + vitest@4.1.10(@types/node@26.1.1)(@vitest/browser-playwright@4.1.10)(jsdom@27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.0 + expect-type: 1.4.0 magic-string: 0.30.21 obug: 2.1.3 pathe: 2.0.3 - picomatch: 4.0.4 + picomatch: 4.0.5 std-env: 4.1.0 tinybench: 2.9.0 tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 26.0.0 - '@vitest/browser-playwright': 4.1.9(bufferutil@4.0.9)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(playwright@1.61.0)(utf-8-validate@5.0.10)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9) + '@types/node': 26.1.1 + '@vitest/browser-playwright': 4.1.10(bufferutil@4.0.9)(msw@2.15.0(@types/node@26.1.1)(typescript@7.0.2))(playwright@1.61.1)(utf-8-validate@5.0.10)(vite@8.1.4(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.2)(sass@1.85.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0))(vitest@4.1.10) jsdom: 27.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - msw @@ -23142,18 +24449,17 @@ snapshots: w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - optional: true - wagmi@3.6.14(33458ed1d2c332a5e309c6e4b0d54aab): + wagmi@3.7.1(23bf2f6635616981ad1339a134a5f675): dependencies: - '@tanstack/react-query': 5.100.10(react@19.2.7) - '@wagmi/connectors': 8.0.13(abec6eed1e25c11127302d84f2389259) - '@wagmi/core': 3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)) + '@tanstack/react-query': 5.101.2(react@19.2.7) + '@wagmi/connectors': 8.0.22(daddac613d0afa74a978e493d0be8db1) + '@wagmi/core': 3.6.1(@tanstack/query-core@5.101.2)(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(typescript@7.0.2)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76)) react: 19.2.7 use-sync-external-store: 1.4.0(react@19.2.7) - viem: 2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.55.0(bufferutil@4.0.9)(typescript@7.0.2)(utf-8-validate@5.0.10)(zod@3.25.76) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - '@base-org/account' - '@coinbase/wallet-sdk' @@ -23167,6 +24473,8 @@ snapshots: - immer - porto + walk-up-path@4.0.0: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -23187,8 +24495,7 @@ snapshots: webidl-conversions@3.0.1: {} - webidl-conversions@8.0.1: - optional: true + webidl-conversions@8.0.1: {} webpack-virtual-modules@0.6.2: {} @@ -23200,21 +24507,17 @@ snapshots: whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 - optional: true whatwg-fetch@3.6.20: {} - whatwg-mimetype@4.0.0: - optional: true + whatwg-mimetype@4.0.0: {} - whatwg-mimetype@5.0.0: - optional: true + whatwg-mimetype@5.0.0: {} whatwg-url@15.1.0: dependencies: tr46: 6.0.0 webidl-conversions: 8.0.1 - optional: true whatwg-url@5.0.0: dependencies: @@ -23294,11 +24597,6 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 5.0.10 - ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10): optionalDependencies: bufferutil: 4.0.9 @@ -23324,27 +24622,14 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 5.0.10 - ws@8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - - ws@8.20.1(bufferutil@4.0.9)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - optional: true - ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): optionalDependencies: bufferutil: 4.0.9 utf-8-validate: 5.0.10 - xml-name-validator@5.0.0: - optional: true + xml-name-validator@5.0.0: {} - xmlchars@2.2.0: - optional: true + xmlchars@2.2.0: {} xmlhttprequest-ssl@2.1.2: {} @@ -23364,8 +24649,6 @@ snapshots: - bufferutil - utf-8-validate - xstate@5.31.1: {} - xstream@11.14.0: dependencies: globalthis: 1.0.4 @@ -23444,25 +24727,25 @@ snapshots: zod@4.4.3: {} - zustand@5.0.0(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)): + zustand@5.0.0(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)): optionalDependencies: '@types/react': 19.2.17 - immer: 11.1.8 + immer: 11.1.11 react: 19.2.7 use-sync-external-store: 1.4.0(react@19.2.7) - zustand@5.0.14(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)): + zustand@5.0.14(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)): optionalDependencies: '@types/react': 19.2.17 - immer: 11.1.8 + immer: 11.1.11 react: 19.2.7 use-sync-external-store: 1.4.0(react@19.2.7) optional: true - zustand@5.0.3(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)): + zustand@5.0.3(@types/react@19.2.17)(immer@11.1.11)(react@19.2.7)(use-sync-external-store@1.4.0(react@19.2.7)): optionalDependencies: '@types/react': 19.2.17 - immer: 11.1.8 + immer: 11.1.11 react: 19.2.7 use-sync-external-store: 1.4.0(react@19.2.7) optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd60fa2cb..51129f84f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,109 +2,143 @@ packages: - packages/* - packages/examples/* +allowBuilds: + "@ast-grep/cli": true + '@parcel/watcher': false + '@reown/appkit': false + '@stellar/stellar-sdk': false + blake-hash: false + bufferutil: false + chacha-native: false + esbuild: false + msgpackr-extract: false + msw: false + protobufjs: false + sharp: false + tiny-secp256k1: false + usb: false + utf-8-validate: false + catalog: - "@biomejs/biome": ^2.5.0 - "@commitlint/cli": ^21.0.2 - "@commitlint/config-conventional": ^21.0.2 + "@ast-grep/cli": 0.44.1 + "@biomejs/biome": ^2.5.3 + "@commitlint/cli": ^21.2.1 + "@commitlint/config-conventional": ^21.2.0 "@cosmjs/amino": ^0.36.2 "@cosmjs/encoding": 0.36.2 "@cosmos-kit/core": 2.18.1 "@cosmos-kit/keplr": ^2.17.1 "@cosmos-kit/leap": ^2.17.1 "@cosmos-kit/walletconnect": 2.15.1 - "@effect/openapi-generator": 4.0.0-beta.68 - "@effect/platform-node": 4.0.0-beta.68 - "@faker-js/faker": ^10.4.0 - "@ledgerhq/wallet-api-client": ^1.14.5 - "@ledgerhq/wallet-api-core": ^1.32.1 + "@effect/atom-react": 4.0.0-beta.97 + "@effect/openapi-generator": 4.0.0-beta.97 + "@effect/platform-browser": 4.0.0-beta.97 + "@effect/platform-node": 4.0.0-beta.97 + "@faker-js/faker": ^10.5.0 + "@ledgerhq/wallet-api-client": ^1.15.1 + "@ledgerhq/wallet-api-core": ^1.34.0 "@luno-kit/core": ^0.0.13 - "@meshsdk/wallet": 1.9.0-beta-40 + "@meshsdk/wallet": 1.9.1 "@polkadot/types": ^16.5.6 "@polkadot/util": ^14.0.3 - "@radix-ui/react-dialog": ^1.1.15 - "@radix-ui/react-dropdown-menu": ^2.1.16 - "@radix-ui/react-tooltip": ^1.2.8 - "@radix-ui/react-visually-hidden": ^1.2.4 + "@radix-ui/react-dialog": ^1.1.19 + "@radix-ui/react-dropdown-menu": ^2.1.20 + "@radix-ui/react-tooltip": ^1.2.12 + "@radix-ui/react-visually-hidden": ^1.2.7 "@rolldown/plugin-babel": ^0.2.3 "@safe-global/safe-apps-provider": ^0.18.6 "@safe-global/safe-apps-sdk": ^9.1.0 + "@solana-mobile/wallet-adapter-mobile": ^2.2.3 "@solana/wallet-adapter-base": ^0.9.27 - "@solana/wallet-adapter-react": ^0.15.39 "@solana/wallet-adapter-wallets": ^0.19.38 + "@solana/wallet-standard-wallet-adapter-base": ^1.1.4 "@solana/web3.js": ^1.98.4 "@stakekit/rainbowkit": ^2.2.11 - "@tanstack/react-query": ^5.100.10 - "@tanstack/react-virtual": ^3.13.24 + "@tanstack/react-query": ^5.101.2 + "@tanstack/react-virtual": ^3.14.5 "@ton/core": ^0.63.1 "@tonconnect/ui": ^2.4.4 - "@tronweb3/tronwallet-abstract-adapter": ^1.1.13 - "@tronweb3/tronwallet-adapter-bitkeep": ^1.1.8 - "@tronweb3/tronwallet-adapter-ledger": ^1.1.13 - "@tronweb3/tronwallet-adapter-tronlink": ^1.1.16 - "@tronweb3/tronwallet-adapter-walletconnect": ^3.0.3 + "@tronweb3/tronwallet-abstract-adapter": ^1.2.0 + "@tronweb3/tronwallet-adapter-bitkeep": ^1.2.0 + "@tronweb3/tronwallet-adapter-ledger": ^1.1.14 + "@tronweb3/tronwallet-adapter-tronlink": ^1.2.0 + "@tronweb3/tronwallet-adapter-walletconnect": ^3.0.5 "@types/lodash.merge": ^4.6.9 "@types/lodash.uniqwith": ^4.5.9 - "@types/node": ^25.7.0 + "@types/node": ^26.1.1 "@types/react": 19.2.17 "@types/react-dom": 19.2.3 "@types/react-is": ^19.2.0 - "@vanilla-extract/css": ^1.20.1 + "@typescript/native": npm:typescript@^7.0.2 + "@vanilla-extract/css": ^1.21.1 "@vanilla-extract/dynamic": ^2.1.5 "@vanilla-extract/recipes": ^0.5.7 - "@vanilla-extract/sprinkles": ^1.6.5 - "@vanilla-extract/vite-plugin": ^5.2.2 - "@vitejs/plugin-react": ^6.0.2 - "@vitest/browser-playwright": ^4.1.9 - "@xstate/react": ^6.1.0 - "@xstate/store": ^3.17.5 - autoprefixer: ^10.5.0 + "@vanilla-extract/sprinkles": ^1.7.0 + "@vanilla-extract/vite-plugin": ^5.2.4 + "@vitejs/plugin-react": ^6.0.3 + "@vitest/browser-playwright": ^4.1.10 + "@wallet-standard/app": ^1.1.0 + "@wallet-standard/base": ^1.1.0 + autoprefixer: ^10.5.2 babel-plugin-react-compiler: 1.0.0 - bignumber.js: ^11.1.1 + bignumber.js: ^11.1.5 chain-registry: 1.69.221 clsx: ^2.1.1 cosmjs-types: ^0.10.1 - date-fns: ^4.1.0 - effect: 4.0.0-beta.68 + effect: 4.0.0-beta.97 eventemitter3: ^5.0.4 husky: ^9.1.7 - i18next: ^26.1.0 + i18next: ^26.3.6 i18next-browser-languagedetector: ^8.2.1 + jsdom: ^27.2.0 + knip: ^6.23.0 lodash.merge: ^4.6.2 lodash.uniqwith: ^4.5.0 mipd: ^0.0.7 - mixpanel-browser: ^2.78.0 - motion: 12.38.0 - msw: ^2.14.6 - next: ^16.2.9 - playwright: ^1.61.0 - postcss: ^8.5.14 - purify-ts: 2.1.4 + mixpanel-browser: ^2.81.0 + motion: 12.42.2 + msw: ^2.15.0 + next: ^16.2.10 + playwright: ^1.61.1 + postcss: ^8.5.16 react: ^19.2.7 react-dom: ^19.2.7 - react-i18next: ^17.0.7 + react-i18next: ^17.0.9 react-is: ^19.2.0 react-loading-skeleton: ^3.5.0 react-router: ^7.15.0 - recharts: ^3.8.1 - reselect: ^5.1.1 - rev-dep: ^2.15.0 - rxjs: ^7.8.2 + recharts: ^3.9.2 + rev-dep: ^2.18.0 serve: ^14.2.6 swagger2openapi: ^7.0.8 - tsx: ^4.22.4 - turbo: ^2.9.18 - typescript: ^6.0.3 + tsx: ^4.23.0 + turbo: ^2.10.4 + typescript: npm:@typescript/typescript6@^6.0.2 unplugin-macros: ^0.20.2 - viem: ^2.48.11 - vite: ^8.0.16 + viem: ^2.55.0 + vite: ^8.1.4 vite-plugin-node-polyfills: ^0.28.0 - vitest: ^4.1.9 + vitest: ^4.1.10 vitest-browser-react: ^2.2.0 - wagmi: 3.6.14 - xstate: ^5.31.1 + wagmi: 3.7.1 yaml: ^2.9.0 minimumReleaseAge: 4320 minimumReleaseAgeExclude: - "@stakekit/rainbowkit" + - "@typescript/typescript-*" + - typescript + +packageExtensions: + "@fractalwagmi/solana-wallet-adapter@0.1.1": + peerDependencies: + "@solana/web3.js": ^1.98.4 + +patchedDependencies: + "@stakekit/rainbowkit@2.2.11": patches/@stakekit__rainbowkit@2.2.11.patch + +peerDependencyRules: + allowedVersions: + "i18next>typescript": "7" + "react-i18next>typescript": "7" diff --git a/scripts/check-test-only-exports.ts b/scripts/check-test-only-exports.ts new file mode 100644 index 000000000..3846ee890 --- /dev/null +++ b/scripts/check-test-only-exports.ts @@ -0,0 +1,186 @@ +import { spawnSync } from "node:child_process"; +import { dirname, resolve } from "node:path"; +import type { KnipConfig } from "knip"; + +type IssueType = "exports" | "types"; + +type KnipSymbol = { + readonly name: string; + readonly line?: number; + readonly col?: number; +}; + +type KnipFileIssues = { + readonly file: string; + readonly exports?: ReadonlyArray; + readonly types?: ReadonlyArray; +}; + +type KnipReport = { + readonly issues: ReadonlyArray; +}; + +type SymbolFinding = KnipSymbol & { + readonly file: string; + readonly issueType: IssueType; +}; + +const knipConfig = { + ignoreExportsUsedInFile: true, + workspaces: { + "packages/widget": { + entry: [ + "src/index.package.ts!", + "src/index.bundle.ts!", + "src/public-api/index.package.ts!", + "src/public-api/index.bundle.ts!", + "src/main.tsx!", + "src/translation/i18next.d.ts!", + "src/types/purify-extend.d.ts!", + "src/types/window.d.ts!", + "src/vite-env.d.ts!", + "postcss.config.js!", + "vite/*.ts!", + "tests/utils/setup.browser.ts", + "tests/utils/setup.dom.ts", + "tests/**/*.test.ts", + "tests/**/*.test.tsx", + "scripts/*.test.ts", + "scripts/prepare-canary-release.ts!", + "scripts/generate-effect-openapi.ts!", + ], + project: [ + "src/**/*.{ts,tsx}!", + "scripts/*.{ts,tsx}", + "tests/**/*.{ts,tsx}", + "vite/**/*.ts!", + ], + ignoreIssues: { + "src/generated/api/legacy-schema.ts": ["exports", "types"], + "src/types/yield-api-schema.d.ts": ["exports", "types"], + }, + }, + }, +} satisfies KnipConfig; + +export default knipConfig; + +const configOnlyEnvironmentVariable = "KNIP_TEST_ONLY_EXPORTS_CONFIG"; +const scriptPath = resolve(process.argv[1] ?? ""); +const repoRoot = resolve(dirname(scriptPath), ".."); +const issueTypes = [ + "exports", + "types", +] as const satisfies ReadonlyArray; +const baseArgs = [ + "--config", + scriptPath, + "--workspace", + "@stakekit/widget", + "--include", + issueTypes.join(","), + "--reporter", + "json", + "--no-progress", + "--no-config-hints", + "--no-exit-code", +]; + +const runKnip = ({ + production, +}: { + readonly production: boolean; +}): KnipReport => { + const result = spawnSync( + "knip", + production ? ["--production", ...baseArgs] : baseArgs, + { + cwd: repoRoot, + encoding: "utf8", + env: { + ...process.env, + [configOnlyEnvironmentVariable]: "1", + NO_COLOR: "1", + }, + } + ); + + if (result.error) { + throw result.error; + } + + if (result.status !== 0) { + throw new Error( + result.stderr || `Knip exited with status ${result.status}` + ); + } + + try { + return JSON.parse(result.stdout) as KnipReport; + } catch (error) { + throw new Error(`Failed to parse Knip JSON output: ${result.stdout}`, { + cause: error, + }); + } +}; + +const collectSymbols = ({ + issues, +}: KnipReport): ReadonlyMap => { + const symbols = new Map(); + + for (const issue of issues) { + for (const issueType of issueTypes) { + for (const symbol of issue[issueType] ?? []) { + const key = JSON.stringify([issue.file, issueType, symbol.name]); + symbols.set(key, { + ...symbol, + file: issue.file, + issueType, + }); + } + } + } + + return symbols; +}; + +const main = () => { + const productionSymbols = collectSymbols(runKnip({ production: true })); + const allSymbols = collectSymbols(runKnip({ production: false })); + const testOnlySymbols = [...productionSymbols] + .filter(([key]) => !allSymbols.has(key)) + .map(([, symbol]) => symbol) + .sort( + (left, right) => + left.file.localeCompare(right.file) || + left.name.localeCompare(right.name) + ); + + if (testOnlySymbols.length === 0) { + console.log("No test-only imports of otherwise unused runtime symbols."); + return; + } + + console.error( + `Test-only imports of otherwise unused runtime symbols (${testOnlySymbols.length}):` + ); + + let currentFile: string | undefined; + for (const symbol of testOnlySymbols) { + if (symbol.file !== currentFile) { + currentFile = symbol.file; + console.error(` ${currentFile}`); + } + + const typeLabel = symbol.issueType === "types" ? " (type)" : ""; + const location = symbol.line ? `:${symbol.line}:${symbol.col ?? 1}` : ""; + console.error(` - ${symbol.name}${typeLabel}${location}`); + } + + process.exitCode = 1; +}; + +if (process.env[configOnlyEnvironmentVariable] !== "1") { + main(); +} diff --git a/sgconfig.yml b/sgconfig.yml new file mode 100644 index 000000000..4c45a1c95 --- /dev/null +++ b/sgconfig.yml @@ -0,0 +1,7 @@ +ruleDirs: + - tools/ast-grep/rules +testConfigs: + - testDir: tools/ast-grep/rule-tests +languageGlobs: + tsx: + - "*.ts" diff --git a/tools/ast-grep/rule-tests/no-ad-hoc-atom-runtime-test.yml b/tools/ast-grep/rule-tests/no-ad-hoc-atom-runtime-test.yml new file mode 100644 index 000000000..dfcfc6849 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-ad-hoc-atom-runtime-test.yml @@ -0,0 +1,6 @@ +id: no-ad-hoc-atom-runtime +valid: + - "appRuntime.atom(program)" + - "walletRuntime.fn(program)" +invalid: + - "Atom.runtime(layer)" diff --git a/tools/ast-grep/rule-tests/no-console-in-effect-test.yml b/tools/ast-grep/rule-tests/no-console-in-effect-test.yml new file mode 100644 index 000000000..667a04906 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-console-in-effect-test.yml @@ -0,0 +1,10 @@ +id: no-console-in-effect +valid: + - 'Effect.gen(function* () { yield* Effect.logInfo("done") })' + - 'const render = () => console.log("done")' +invalid: + - 'Effect.gen(function* () { console.log("done") })' + - 'Effect.fn("program")(function* () { console.error("failed") })' + - 'Effect.sync(() => console.log("done"))' + - 'Effect.tryPromise({ try: async () => value, catch: error => { console.error(error); return error } })' + - 'Effect.tap(program, () => console.info("done"))' diff --git a/tools/ast-grep/rule-tests/no-direct-effect-runtime-execution-test.yml b/tools/ast-grep/rule-tests/no-direct-effect-runtime-execution-test.yml new file mode 100644 index 000000000..ffa77ddea --- /dev/null +++ b/tools/ast-grep/rule-tests/no-direct-effect-runtime-execution-test.yml @@ -0,0 +1,11 @@ +id: no-direct-effect-runtime-execution +valid: + - "runWalletEffect(program)" + - "appRuntime.atom(program)" + - "FiberSet.makeRuntimePromise()" +invalid: + - "Effect.runPromise(program)" + - "Effect.runPromiseWith(context)(program)" + - "Effect.runFork(program)" + - "Effect.runSync(program)" + - "Effect.runCallback(program)" diff --git a/tools/ast-grep/rule-tests/no-global-fetch-test.yml b/tools/ast-grep/rule-tests/no-global-fetch-test.yml new file mode 100644 index 000000000..bceab8ca2 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-global-fetch-test.yml @@ -0,0 +1,8 @@ +id: no-global-fetch +valid: + - 'client.execute(request)' + - 'object.fetch(request)' +invalid: + - 'fetch(url)' + - 'window.fetch(url)' + - 'globalThis.fetch(url)' diff --git a/tools/ast-grep/rule-tests/no-json-parse-test.yml b/tools/ast-grep/rule-tests/no-json-parse-test.yml new file mode 100644 index 000000000..a27bafad8 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-json-parse-test.yml @@ -0,0 +1,8 @@ +id: no-json-parse +valid: + - 'Schema.decodeUnknown(Schema.fromJsonString(Payload))(input)' + - 'JSON.stringify(value)' +invalid: + - 'JSON.parse(input)' + - 'globalThis.JSON.parse(input)' + - 'const value = JSON.parse(input) as unknown' diff --git a/tools/ast-grep/rule-tests/no-literal-effect-failure-test.yml b/tools/ast-grep/rule-tests/no-literal-effect-failure-test.yml new file mode 100644 index 000000000..43dbe7d71 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-literal-effect-failure-test.yml @@ -0,0 +1,14 @@ +id: no-literal-effect-failure +valid: + - 'Effect.fail(new AppError({ message: "boom" }))' + - 'Effect.fail(error)' + - 'Effect.fail({ _tag: "AppError", message: "boom" })' +invalid: + - 'Effect.fail("boom")' + - 'Effect.fail(42)' + - 'Effect.fail(false)' + - 'Effect.fail(null)' + - 'Effect.fail(undefined)' + - 'Effect.fail(`failed: ${reason}`)' + - 'Effect.fail({ message: "boom" })' + - 'Effect.fail(["boom"])' diff --git a/tools/ast-grep/rule-tests/no-local-storage-test.yml b/tools/ast-grep/rule-tests/no-local-storage-test.yml new file mode 100644 index 000000000..ccbd80531 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-local-storage-test.yml @@ -0,0 +1,12 @@ +id: no-local-storage +valid: + - 'persistence.get(key)' + - 'const order = ["localStorage"]' + - 'const storage = injectedStorage' +invalid: + - 'localStorage.getItem(key)' + - 'window.localStorage.setItem(key, value)' + - 'globalThis.localStorage.removeItem(key)' + - 'window["localStorage"].clear()' + - 'localStorage[key]' + - 'const storage = window.localStorage' diff --git a/tools/ast-grep/rule-tests/no-native-date-test.yml b/tools/ast-grep/rule-tests/no-native-date-test.yml new file mode 100644 index 000000000..f17ac1764 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-native-date-test.yml @@ -0,0 +1,16 @@ +id: no-native-date +valid: + - 'const now = yield* DateTime.now' + - 'const timestamp: DateTime.Utc = DateTime.makeUnsafe(input)' + - 'const date = DateTime.toEpochMillis(timestamp)' + - 'const schema = Schema.DateTimeUtcFromString' +invalid: + - 'const now = new Date()' + - 'const timestamp: Date = input' + - 'const parsed = Date.parse(input)' + - 'const parsed = window.Date.parse(input)' + - 'const parsed = globalThis.Date.parse(input)' + - 'const value = DateTime.toDate(timestamp)' + - 'const value = DateTime.toDateUtc(timestamp)' + - 'const schema = Schema.DateFromString' + - 'const schema = Schema.DateTimeUtcFromDate' diff --git a/tools/ast-grep/rule-tests/no-native-effect-failure-test.yml b/tools/ast-grep/rule-tests/no-native-effect-failure-test.yml new file mode 100644 index 000000000..076f3cff2 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-native-effect-failure-test.yml @@ -0,0 +1,12 @@ +id: no-native-effect-failure +valid: + - 'Effect.fail(new AppError({ message: "boom" }))' + - 'Effect.tryPromise({ try: load, catch: cause => new AppError({ cause }) })' + - 'Effect.mapError(cause => new AppError({ cause }))' + - 'throw new Error("third-party callback contract")' +invalid: + - 'Effect.fail(new Error("boom"))' + - 'Effect.fail(TypeError("boom"))' + - 'Effect.mapError(cause => new Error("boom", { cause }))' + - 'Effect.try({ try: load, catch: cause => new Error("boom", { cause }) })' + - 'Effect.tryPromise({ try: load, catch: (cause) => new TypeError("boom", { cause }) })' diff --git a/tools/ast-grep/rule-tests/no-react-query-outside-provider-test.yml b/tools/ast-grep/rule-tests/no-react-query-outside-provider-test.yml new file mode 100644 index 000000000..2b56ab479 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-react-query-outside-provider-test.yml @@ -0,0 +1,7 @@ +id: no-react-query-outside-provider +valid: + - 'import { Effect } from "effect"' + - 'import type { QueryClient } from "./query-client"' +invalid: + - 'import { useQuery } from "@tanstack/react-query"' + - 'import { QueryClient } from "@tanstack/react-query/core"' diff --git a/tools/ast-grep/rule-tests/no-source-less-type-export-test.yml b/tools/ast-grep/rule-tests/no-source-less-type-export-test.yml new file mode 100644 index 000000000..24026e69c --- /dev/null +++ b/tools/ast-grep/rule-tests/no-source-less-type-export-test.yml @@ -0,0 +1,17 @@ +id: no-source-less-type-export +valid: + - 'export type { Model } from "./model"' + - 'export { type Model } from "./model"' + - 'export type Model = { readonly id: string }' + - 'export type {}' + - 'export { value }' +invalid: + - |- + import type { Model } from "./model" + export type { Model } + - |- + import { type Model, value } from "./model" + export type { Model } + - |- + import type { Model } from "./model" + export { type Model } diff --git a/tools/ast-grep/rule-tests/no-throw-in-effect-generator-test.yml b/tools/ast-grep/rule-tests/no-throw-in-effect-generator-test.yml new file mode 100644 index 000000000..3c28a4380 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-throw-in-effect-generator-test.yml @@ -0,0 +1,9 @@ +id: no-throw-in-effect-generator +valid: + - 'Effect.gen(function* () { yield* new AppError({ message: "boom" }) })' + - 'Effect.fn("program")(function* () { yield* Effect.die("boom") })' + - 'Effect.gen(function* () { yield* Effect.try({ try: () => { throw new AppError({ message: "boom" }) }, catch: cause => new AppError({ cause }) }) })' + - 'const plain = () => { throw new AppError({ message: "boom" }) }' +invalid: + - 'Effect.gen(function* () { throw new AppError({ message: "boom" }) })' + - 'Effect.fn("program")(function* () { if (bad) throw new AppError({ message: "boom" }) })' diff --git a/tools/ast-grep/rule-tests/no-unsafe-date-time-test.yml b/tools/ast-grep/rule-tests/no-unsafe-date-time-test.yml new file mode 100644 index 000000000..474152cd3 --- /dev/null +++ b/tools/ast-grep/rule-tests/no-unsafe-date-time-test.yml @@ -0,0 +1,9 @@ +id: no-unsafe-date-time +valid: + - 'const now = yield* DateTime.now' + - 'const now = yield* Clock.currentTimeMillis' + - 'const elapsed = DateTime.distance(start, end)' +invalid: + - 'const now = DateTime.nowUnsafe()' + - 'const expired = DateTime.isPastUnsafe(timestamp)' + - 'const scheduled = DateTime.isFutureUnsafe(timestamp)' diff --git a/tools/ast-grep/rules/no-ad-hoc-atom-runtime.yml b/tools/ast-grep/rules/no-ad-hoc-atom-runtime.yml new file mode 100644 index 000000000..0fb638eea --- /dev/null +++ b/tools/ast-grep/rules/no-ad-hoc-atom-runtime.yml @@ -0,0 +1,14 @@ +id: no-ad-hoc-atom-runtime +language: Tsx +severity: error +message: Widget runtime construction belongs in the approved app runtime modules. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** + - packages/widget/src/app/runtime/app-runtime.ts + - packages/widget/src/app/runtime/application-router-runtime.ts + - packages/widget/src/app/runtime/wallet-runtime.ts +rule: + pattern: Atom.runtime($$$ARGS) diff --git a/tools/ast-grep/rules/no-console-in-effect.yml b/tools/ast-grep/rules/no-console-in-effect.yml new file mode 100644 index 000000000..e220e61b6 --- /dev/null +++ b/tools/ast-grep/rules/no-console-in-effect.yml @@ -0,0 +1,16 @@ +id: no-console-in-effect +language: Tsx +severity: error +message: Use Effect.log* inside Effect programs. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + pattern: console.$METHOD($$$ARGS) + inside: + all: + - kind: call_expression + - regex: ^Effect\.(?:gen|fn|sync|suspend|try|tryPromise|tap|tapError)\b + stopBy: end diff --git a/tools/ast-grep/rules/no-direct-effect-runtime-execution.yml b/tools/ast-grep/rules/no-direct-effect-runtime-execution.yml new file mode 100644 index 000000000..6881bd383 --- /dev/null +++ b/tools/ast-grep/rules/no-direct-effect-runtime-execution.yml @@ -0,0 +1,14 @@ +id: no-direct-effect-runtime-execution +language: Tsx +severity: error +message: Run Effects through the scoped Atom runtime or an injected scoped runner. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + pattern: Effect.$METHOD($$$ARGS) +constraints: + METHOD: + regex: ^run(?:Promise|PromiseExit|Fork|Sync|SyncExit|Callback)(?:With)?$ diff --git a/tools/ast-grep/rules/no-global-fetch.yml b/tools/ast-grep/rules/no-global-fetch.yml new file mode 100644 index 000000000..fc72be073 --- /dev/null +++ b/tools/ast-grep/rules/no-global-fetch.yml @@ -0,0 +1,14 @@ +id: no-global-fetch +language: Tsx +severity: error +message: Use Effect HttpClient instead of fetch. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + pattern: $FETCH($$$ARGS) +constraints: + FETCH: + regex: ^(?:(?:globalThis|window)\.)?fetch$ diff --git a/tools/ast-grep/rules/no-json-parse.yml b/tools/ast-grep/rules/no-json-parse.yml new file mode 100644 index 000000000..bc9203357 --- /dev/null +++ b/tools/ast-grep/rules/no-json-parse.yml @@ -0,0 +1,14 @@ +id: no-json-parse +language: Tsx +severity: error +message: Decode JSON strings with Schema.fromJsonString. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + pattern: $JSON.parse($$$ARGS) +constraints: + JSON: + regex: ^(?:(?:globalThis|window)\.)?JSON$ diff --git a/tools/ast-grep/rules/no-literal-effect-failure.yml b/tools/ast-grep/rules/no-literal-effect-failure.yml new file mode 100644 index 000000000..f52cb6542 --- /dev/null +++ b/tools/ast-grep/rules/no-literal-effect-failure.yml @@ -0,0 +1,27 @@ +id: no-literal-effect-failure +language: Tsx +severity: error +message: Pass a tagged domain error to Effect.fail. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + pattern: Effect.fail($FAILURE) +constraints: + FAILURE: + any: + - kind: string + - kind: template_string + - kind: number + - kind: "true" + - kind: "false" + - kind: "null" + - all: + - kind: object + - not: + has: + regex: '^\s*_tag\s*:' + - kind: array + - kind: undefined diff --git a/tools/ast-grep/rules/no-local-storage.yml b/tools/ast-grep/rules/no-local-storage.yml new file mode 100644 index 000000000..4f0be6c9b --- /dev/null +++ b/tools/ast-grep/rules/no-local-storage.yml @@ -0,0 +1,17 @@ +id: no-local-storage +language: Tsx +severity: error +message: Use the persistence service instead of localStorage. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + any: + - pattern: localStorage.$PROPERTY + - pattern: localStorage[$PROPERTY] + - pattern: window.localStorage + - pattern: globalThis.localStorage + - pattern: window["localStorage"] + - pattern: globalThis["localStorage"] diff --git a/tools/ast-grep/rules/no-native-date.yml b/tools/ast-grep/rules/no-native-date.yml new file mode 100644 index 000000000..391a9c0a0 --- /dev/null +++ b/tools/ast-grep/rules/no-native-date.yml @@ -0,0 +1,28 @@ +id: no-native-date +language: Tsx +severity: error +message: Use Effect DateTime at application boundaries instead of native Date. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx + - packages/widget/tests/**/*.ts + - packages/widget/tests/**/*.tsx +ignores: + - packages/widget/src/generated/** + - packages/widget/tests/generated/** +rule: + any: + - kind: identifier + regex: ^Date$ + - kind: type_identifier + regex: ^Date$ + - pattern: globalThis.Date + - pattern: window.Date + - pattern: DateTime.fromDateUnsafe + - pattern: DateTime.nowAsDate + - pattern: DateTime.toDate + - pattern: DateTime.toDateUtc + - pattern: Schema.Date + - pattern: Schema.DateFromString + - pattern: Schema.DateTimeUtcFromDate + - pattern: Schema.DateValid diff --git a/tools/ast-grep/rules/no-native-effect-failure.yml b/tools/ast-grep/rules/no-native-effect-failure.yml new file mode 100644 index 000000000..623e5562b --- /dev/null +++ b/tools/ast-grep/rules/no-native-effect-failure.yml @@ -0,0 +1,22 @@ +id: no-native-effect-failure +language: Tsx +severity: error +message: Map expected Effect failures to a tagged domain error. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + any: + - pattern: Effect.fail(new $ERROR($$$ARGS)) + - pattern: Effect.fail($ERROR($$$ARGS)) + - pattern: Effect.mapError(($ERR) => new $ERROR($$$ARGS)) + - pattern: Effect.mapError($ERR => new $ERROR($$$ARGS)) + - pattern: "Effect.try({ try: $TRY, catch: ($ERR) => new $ERROR($$$ARGS) })" + - pattern: "Effect.try({ try: $TRY, catch: $ERR => new $ERROR($$$ARGS) })" + - pattern: "Effect.tryPromise({ try: $TRY, catch: ($ERR) => new $ERROR($$$ARGS) })" + - pattern: "Effect.tryPromise({ try: $TRY, catch: $ERR => new $ERROR($$$ARGS) })" +constraints: + ERROR: + regex: ^(?:(?:globalThis|window)\.)?(?:Error|TypeError|RangeError|ReferenceError|SyntaxError|URIError|EvalError|AggregateError)$ diff --git a/tools/ast-grep/rules/no-react-query-outside-provider.yml b/tools/ast-grep/rules/no-react-query-outside-provider.yml new file mode 100644 index 000000000..f460d15cf --- /dev/null +++ b/tools/ast-grep/rules/no-react-query-outside-provider.yml @@ -0,0 +1,14 @@ +id: no-react-query-outside-provider +language: Tsx +severity: error +message: React Query is reserved for the third-party Wagmi/RainbowKit provider. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/app/composition/providers/query-client/** +rule: + kind: import_statement + has: + kind: string + regex: ^[\"']@tanstack/react-query(?:/[^\"']*)?[\"']$ diff --git a/tools/ast-grep/rules/no-source-less-type-export.yml b/tools/ast-grep/rules/no-source-less-type-export.yml new file mode 100644 index 000000000..8fd635a30 --- /dev/null +++ b/tools/ast-grep/rules/no-source-less-type-export.yml @@ -0,0 +1,18 @@ +id: no-source-less-type-export +language: Tsx +severity: error +message: Export local types at their definition and import external types from their defining module. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/index.package.ts + - packages/widget/src/index.bundle.ts + - packages/widget/src/public-api/index.package.ts + - packages/widget/src/public-api/index.bundle.ts +rule: + all: + - kind: export_statement + - regex: ^export\s+(?:type\s*\{\s*[^}\s]|\{\s*type\b) + - not: + regex: \bfrom\s+["'] diff --git a/tools/ast-grep/rules/no-throw-in-effect-generator.yml b/tools/ast-grep/rules/no-throw-in-effect-generator.yml new file mode 100644 index 000000000..c9026e5c2 --- /dev/null +++ b/tools/ast-grep/rules/no-throw-in-effect-generator.yml @@ -0,0 +1,25 @@ +id: no-throw-in-effect-generator +language: Tsx +severity: error +message: Use yield* for expected failures or Effect.die for defects. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx +ignores: + - packages/widget/src/generated/** +rule: + kind: throw_statement + inside: + all: + - kind: generator_function + - inside: + any: + - pattern: Effect.gen($$$ARGS) + - pattern: Effect.fn($$$FIRST)($$$SECOND) + stopBy: end + stopBy: + any: + - kind: arrow_function + - kind: function_expression + - kind: function_declaration + - kind: generator_function diff --git a/tools/ast-grep/rules/no-unsafe-date-time.yml b/tools/ast-grep/rules/no-unsafe-date-time.yml new file mode 100644 index 000000000..b9ebe9d4d --- /dev/null +++ b/tools/ast-grep/rules/no-unsafe-date-time.yml @@ -0,0 +1,17 @@ +id: no-unsafe-date-time +language: Tsx +severity: error +message: Use Effect Clock or DateTime.now; unsafe wall-clock access is reserved for the synchronous WalletConnect adapter. +files: + - packages/widget/src/**/*.ts + - packages/widget/src/**/*.tsx + - packages/widget/tests/**/*.ts + - packages/widget/tests/**/*.tsx +ignores: + - packages/widget/src/generated/** + - packages/widget/tests/generated/** +rule: + any: + - pattern: DateTime.isFutureUnsafe + - pattern: DateTime.isPastUnsafe + - pattern: DateTime.nowUnsafe diff --git a/tsconfig.base.json b/tsconfig.base.json index 74330d539..6a86bb6be 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -7,6 +7,7 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, + "noUncheckedIndexedAccess": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Bundler", diff --git a/tsconfig.json b/tsconfig.json index 9dbbd6d8f..1258ee871 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "tsBuildInfoFile": "dist/.tsbuildinfo/tsconfig.tsbuildinfo" }, - "exclude": ["node_modules", "repos"] + "exclude": ["node_modules", "@repos"] } diff --git a/turbo.json b/turbo.json index 0cb775001..df191d08d 100644 --- a/turbo.json +++ b/turbo.json @@ -1,8 +1,9 @@ { "$schema": "https://turbo.build/schema.json", - "dangerouslyDisablePackageManagerCheck": true, "globalDependencies": [ "biome.json", + "sgconfig.yml", + "tools/ast-grep/**", "pnpm-lock.yaml", "pnpm-workspace.yaml", "tsconfig.base.json", @@ -17,6 +18,7 @@ "lint": { "inputs": [ "src/**", + "scripts/**", "tests/**", "tsconfig*.json", "vite/**/*.ts", @@ -24,9 +26,19 @@ "package.json" ] }, + "//#lint:ast": { + "inputs": [ + "packages/widget/src/**/*.{ts,tsx}", + "packages/widget/tests/**/*.{ts,tsx}", + "packages/widget/scripts/**/*.{ts,tsx}", + "sgconfig.yml", + "tools/ast-grep/**" + ] + }, "format": { "inputs": [ "src/**", + "scripts/**", "tests/**", "tsconfig*.json", "vite/**/*.ts", @@ -37,12 +49,16 @@ "test": { "inputs": [ "src/**", + "scripts/**", "tests/**", "tsconfig*.json", "vite/**/*.ts", "vite.config.*", "package.json" ] + }, + "clean": { + "cache": false } } }